declare namespace RPGMakerMV {
    type ProgressListener = (countLoaded: number, countLoading: number) => void

    interface ImageCacheItem {
        bitmap: Bitmap;
        touch: number;
        key: string;
        reservationId: boolean;
    }

    /**
     * Bitmap states(Bitmap._loadingState)
     */
    type BitmapLoadingState =
        'none' |
        'pending' |
        'purged' |
        'requesting' |
        'requestCompleted' |
        'decrypting' |
        'decryptCompleted' |
        'loaded' |
        'error';

    type EventInfo = CommonEventInfo | BattleEventInfo | TestEventInfo | MapEventInfo

    interface CommonEventInfo {
        eventType: 'common_event';
        commonEventId: number;
    }

    interface BattleEventInfo {
        eventType: 'battle_event';
        troopId: number;
    }

    interface TestEventInfo {
        eventType: 'test_event';
    }

    interface MapEventInfo {
        eventType: "map_event";
        mapId: number;
        mapEventId: number;
        page: number;
    }

    type EventError = (CommonEventError | BattleEventError | TestEventError | MapEventError) & {
        eventCommand:
            'set_route_script' |
            'auto_route_script' |
            'conditional_branch_script' |
            'control_variables' |
            'script' |
            'plugin_command' |
            'other';
        content: string;
    }

    type CommonEventError = Error & CommonEventInfo

    type BattleEventError = Error & BattleEventInfo

    type TestEventError = Error & TestEventInfo

    type MapEventError = Error & MapEventInfo
}

declare class ProgressWatcher {
    static _countLoaded: number
    static _countLoading: number
    static _progressListener: RPGMakerMV.ProgressListener
    private constructor();
    static initialize(): void;
    static _bitmapListener(bitmap: Bitmap): void;
    static _audioListener(audio: WebAudio): void;
    static setProgressListener(progressListener: RPGMakerMV.ProgressListener): void;
    static clearProgress(): void;
    static truncateProgress(): void;
}
//=============================================================================
// rpg_core.js v1.6.2
//=============================================================================

//-----------------------------------------------------------------------------
/**
 * This is not a class, but contains some methods that will be added to the
 * standard Javascript objects.
 *
 * @class JsExtensions
 */

class JsExtensions {
    private constructor(){}
}

/*interface String {
    contains(sub: string): boolean;
}

String.prototype.contains = function (sub:string):boolean {
    if (sub === "") {
        return false;
    }
    return (this.indexOf(sub) !== -1);
};*/
interface Number {   
    clamp(min: number, max: number): number;
    mod(n: number): number;
    padZero(length: number): string;
}

/**
 * Returns a number whose value is limited to the given range.
 *
 * @method Number.prototype.clamp
 * @param {Number} min The lower boundary
 * @param {Number} max The upper boundary
 * @return {Number} A number in the range (min, max)
 */
Number.prototype.clamp = function(min: number, max: number): number{
    return Math.min(Math.max(this.valueOf(), min), max);
};

/**
 * Returns a modulo value which is always positive.
 *
 * @method Number.prototype.mod
 * @param {Number} n The divisor
 * @return {Number} A modulo value
 */
Number.prototype.mod = function(n: number): number {
    return ((this.valueOf() % n) + n) % n;
};

interface String {  
    format(...args :Array<string | number>): string;
    padZero(length: number): string;
    contains(string: string): boolean;
}

/**
 * Replaces %1, %2 and so on in the string to the arguments.
 *
 * @method String.prototype.format
 * @param {Any} ...args The objects to format
 * @return {String} A formatted string
 */
String.prototype.format = function() {
    var args = arguments;
    return this.replace(/%([0-9]+)/g, function(s, n) {
        return args[Number(n) - 1];
    });
};

/**
 * Makes a number string with leading zeros.
 *
 * @method String.prototype.padZero
 * @param {Number} length The length of the output string
 * @return {String} A string with leading zeros
 */
String.prototype.padZero = function(length: number): string{
    var s = this;
    while (s.length < length) {
        s = '0' + s;
    }
    return s.toString();
};

/**
 * Makes a number string with leading zeros.
 *
 * @method Number.prototype.padZero
 * @param {Number} length The length of the output string
 * @return {String} A string with leading zeros
 */
Number.prototype.padZero = function(length: number): string{
    return String(this).padZero(length);
};

interface Array<T> {  
    equals(array: T[]): boolean;
    clone(): T;
    contains(element: T): boolean;
}

//TODO 后续改造Array.prototype= ....格式
Object.defineProperties(Array.prototype, {

    /**
     * Checks whether the two arrays are same.
     *
     * @method Array.prototype.equals
     * @param {Array} array The array to compare to
     * @return {Boolean} True if the two arrays are same
     */
    equals: {
        enumerable: false,
        value: function(array:any) {
            if (!array || this.length !== array.length) {
                return false;
            }
            for (var i = 0; i < this.length; i++) {
                if (this[i] instanceof Array && array[i] instanceof Array) {
                    if (!this[i].equals(array[i])) {
                        return false;
                    }
                } else if (this[i] !== array[i]) {
                    return false;
                }
            }
            return true;
        }
    },
    /**
     * Makes a shallow copy of the array.
     *
     * @method Array.prototype.clone
     * @return {Array} A shallow copy of the array
     */   
    clone: {
        enumerable: false,
        value: function() {
            return this.slice(0);
        }
    },
    /**
     * Checks whether the array contains a given element.
     *
     * @method Array.prototype.contains
     * @param {Any} element The element to search for
     * @return {Boolean} True if the array contains a given element
     */   
    contains : {
        enumerable: false,
        value: function(element:any) {
            return this.indexOf(element) >= 0;
        }
    }
});

/**
 * Checks whether the string contains a given string.
 *
 * @method String.prototype.contains
 * @param {String} string The string to search for
 * @return {Boolean} True if the string contains a given string
 */
String.prototype.contains = function(string) {
    return this.indexOf(string) >= 0;
};

declare interface Math {
    randomInt(max: number): number;
}

/**
 * Generates a random integer in the range (0, max-1).
 *
 * @static
 * @method Math.randomInt
 * @param {Number} max The upper boundary (excluded)
 * @return {Number} A random integer
 */ 
Math.randomInt = function(max: number): number {
    return Math.floor(max * Math.random());
}; 

//-----------------------------------------------------------------------------
/**
 * The static class that defines utility methods.
 *
 * @class Utils
 */
class Utils {
    private constructor(){
        throw new Error('This is a static class');
    }
    /**
     * The name of the RPG Maker. 'MV' in the current version.
     *
     * @static
     * @property RPGMAKER_NAME
     * @type String
     * @final
     */
    static RPGMAKER_NAME : string = 'MV';
    /**
     * The version of the RPG Maker.
     *
     * @static
     * @property RPGMAKER_VERSION
     * @type String
     * @final
     */
    static RPGMAKER_VERSION: string = "1.6.1";
    
    /**
     * Checks whether the option is in the query string.
     *
     * @static
     * @method isOptionValid
     * @param {String} name The option name
     * @return {Boolean} True if the option is in the query string
     */   
    static isOptionValid(name: string): boolean | number{        
        if (location.search.slice(1).split('&').contains(name)) {return 1;};
        if (typeof nw !== "undefined" && nw.App.argv.length > 0 && nw.App.argv[0].split('&').contains(name)) {return 1;};
        return 0;
    }

    /**
     * Checks whether the platform is NW.js.
     *
     * @static
     * @method isNwjs
     * @return {Boolean} True if the platform is NW.js
     */
    static isNwjs(): boolean{
        return typeof require === 'function' && typeof process === 'object';
    }

    /**
     * Checks whether the platform is NW.js.
     *
     * @static
     * @method isNwjs
     * @return {Boolean} True if the platform is NW.js
     */
    static isMobileDevice(): boolean{
        var r = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
        return !!navigator.userAgent.match(r);
    }

    /**
     * Checks whether the browser is Mobile Safari.
     *
     * @static
     * @method isMobileSafari
     * @return {Boolean} True if the browser is Mobile Safari
     */
    static isMobileSafari(): boolean{
        var agent = navigator.userAgent;
        return !!(agent.match(/iPhone|iPad|iPod/) && agent.match(/AppleWebKit/) &&
                  !agent.match('CriOS'));
    }

    /**
     * Checks whether the browser is Android Chrome.
     *
     * @static
     * @method isAndroidChrome
     * @return {Boolean} True if the browser is Android Chrome
     */
    static isAndroidChrome(): boolean{
        var agent = navigator.userAgent;
        return !!(agent.match(/Android/) && agent.match(/Chrome/));    
    }

    /**
     * Checks whether the browser can read files in the game folder.
     *
     * @static
     * @method canReadGameFiles
     * @return {Boolean} True if the browser can read files in the game folder
     */
    static canReadGameFiles(): boolean{
        var scripts = document.getElementsByTagName('script');
        var lastScript = scripts[scripts.length - 1];
        var xhr = new XMLHttpRequest();
        try {
            xhr.open('GET', lastScript.src);
            xhr.overrideMimeType('text/javascript');
            xhr.send();
            return true;
        } catch (e) {
            return false;
        }
    }

    /**
     * Makes a CSS color string from RGB values.
     *
     * @static
     * @method rgbToCssColor
     * @param {Number} r The red value in the range (0, 255)
     * @param {Number} g The green value in the range (0, 255)
     * @param {Number} b The blue value in the range (0, 255)
     * @return {String} CSS color string
     */
    static rgbToCssColor(r: number, g: number, b: number): string{
        r = Math.round(r);
        g = Math.round(g);
        b = Math.round(b);
        return 'rgb(' + r + ',' + g + ',' + b + ')';
        //return `rgb(${r} , ${g} , ${b})`;
    }

    static _id: number = 1;
    static generateRuntimeId(): number{
        return Utils._id++;
    }

    static _supportPassiveEvent: boolean = null;

    /**
     * Test this browser support passive event feature
     * 
     * @static
     * @method isSupportPassiveEvent
     * @return {Boolean} this browser support passive event or not
     */   
    static isSupportPassiveEvent(): boolean{
        if (typeof Utils._supportPassiveEvent === "boolean") {
            return Utils._supportPassiveEvent;
        }
        // test support passive event
        // https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection
        var passive = false;
        var options = Object.defineProperty({}, "passive", {
            get: function() { passive = true; }
        });
        window.addEventListener("test", null, options);
        Utils._supportPassiveEvent = passive;
        return passive;
    }

}

//-----------------------------------------------------------------------------
/**
 * The resource class. Allows to be collected as a garbage if not use for some time or ticks
 *
 * @class CacheEntry
 * @constructor
 * @param {ResourceManager} resource manager
 * @param {string} key, url of the resource
 * @param {string} item - Bitmap, HTML5Audio, WebAudio - whatever you want to store in the cache
 */
class CacheEntry {
    
    constructor(cache: CacheMap, key: string, item: Bitmap | Html5Audio | WebAudio){
        this.cache = cache;
        this.key = key;
        this.item = item;
    }
    cache: CacheMap;
    key: string;
    item: Bitmap | Html5Audio | WebAudio;
    cached: boolean = false;
    touchTicks: number = 0;
    touchSeconds: number = 0;
    ttlTicks: number = 0;
    ttlSeconds: number = 0;
    freedByTTL: boolean = false;
 
    /**
     * frees the resource
     */
    free(byTTL?: boolean): void{
        this.freedByTTL = byTTL || false;
        if (this.cached) {
            this.cached = false;
            delete this.cache._inner[this.key];
        }
    };
    
    /**
     * Allocates the resource
     * @returns {CacheEntry}
     */
    allocate(): CacheEntry{ 
        if (!this.cached) {
            this.cache._inner[this.key] = this;
            this.cached = true;
        }
        this.touch();
        return this;
    };
    
    /**
     * Sets the time to live
     * @param {number} ticks TTL in ticks, 0 if not set
     * @param {number} time TTL in seconds, 0 if not set
     * @returns {CacheEntry}
     */
    setTimeToLive(ticks?: number, seconds?: number): CacheEntry{
        this.ttlTicks = ticks || 0;
        this.ttlSeconds = seconds || 0;
        return this;
    };

    isStillAlive(): boolean{
        var cache = this.cache;
        return ((this.ttlTicks == 0) || (this.touchTicks + this.ttlTicks < cache.updateTicks )) &&
            ((this.ttlSeconds == 0) || (this.touchSeconds + this.ttlSeconds < cache.updateSeconds ));
    
    };

    /**
     * makes sure that resource wont freed by Time To Live
     * if resource was already freed by TTL, put it in cache again
     */
    touch(): void{
        var cache = this.cache;
        if (this.cached) {
            this.touchTicks = cache.updateTicks;
            this.touchSeconds = cache.updateSeconds;
        } else if (this.freedByTTL) {
            this.freedByTTL = false;
            if (!cache._inner[this.key]) {
                cache._inner[this.key] = this;
            }
        }        
    };
}

/**
 * Cache for images, audio, or any other kind of resource
 * @param manager
 * @constructor
 */
class CacheMap {
    
    constructor(manager: typeof DataManager | typeof ConfigManager | typeof StorageManager | typeof ImageManager | typeof AudioManager | typeof SoundManager | typeof TextManager | typeof SceneManager | typeof BattleManager | typeof PluginManager){
        this.manager = manager;
    }
    manager: typeof DataManager | typeof ConfigManager | typeof StorageManager | typeof ImageManager | typeof AudioManager | typeof SoundManager | typeof TextManager | typeof SceneManager | typeof BattleManager | typeof PluginManager;
    _inner: { [key: string]: CacheEntry } = {};
    _lastRemovedEntries: CacheEntry[];  //TODO 未赋值 {};
    updateTicks: number = 0;
    lastCheckTTL: number = 0;
    delayCheckTTL: number = 100.0;
    updateSeconds: number = Date.now();

    /**
     * checks ttl of all elements and removes dead ones
     */   
    checkTTL(): void{
        var cache = this._inner;
        var temp = this._lastRemovedEntries;
        if (!temp) {
            temp = [];
            this._lastRemovedEntries = temp;
        }
        for (var key in cache) {
            var entry = cache[key];
            if (!entry.isStillAlive()) {
                temp.push(entry);
            }
        }
        for (var i = 0; i < temp.length; i++) {
            temp[i].free(true);
        }
        temp.length = 0;

    };
  
    /**
     * cache item
     * @param key url of cache element
     * @returns {*|null}
     */
    getItem(key: string): any{
        var entry = this._inner[key];
        if (entry) {
            return entry.item;
        }
        return null;
    };

    clear(): void{
        var keys = Object.keys(this._inner);
        for (var i = 0; i < keys.length; i++) {
            this._inner[keys[i]].free();
        }
    };

    setItem(key: string, item: any): CacheEntry{
        return new CacheEntry(this, key, item).allocate();
    };

    update(ticks: number, delta: number): void{
        this.updateTicks += ticks;
        this.updateSeconds += delta;
        if (this.updateSeconds >= this.delayCheckTTL + this.lastCheckTTL) {
            this.lastCheckTTL = this.updateSeconds;
            this.checkTTL();
        }
    };
}

class ImageCache {
    constructor(){
        this.initialize.apply(this, arguments);
    };
    static limit: number = 10 * 1000 * 1000;

    _items:any;

    initialize(): void{
        this._items = {};
    };
    add(key: string, value: Bitmap): void{
        this._items[key] = {
            bitmap: value,
            touch: Date.now(),
            key: key
        };
    
        this._truncateCache();
    };
    get(key: string): Bitmap{
        if(this._items[key]){
            var item = this._items[key];
            item.touch = Date.now();
            return item.bitmap;
        }
    
        return null;
    };
    reserve(key: string, value: Bitmap, reservationId: number): void{
        if(!this._items[key]){
            this._items[key] = {
                bitmap: value,
                touch: Date.now(),
                key: key
            };
        }
    
        this._items[key].reservationId = reservationId;
    };
    releaseReservation(reservationId: number): void{
        var items = this._items;

        Object.keys(items)
            .map(function(key){return items[key];})
            .forEach(function(item){
                if(item.reservationId === reservationId){
                    delete item.reservationId;
                }
            });        
    };
    _truncateCache(): void{
        var items = this._items;
        var sizeLeft = ImageCache.limit;
        //自己声明的对象，为了兼容forEach中的代码
        let _this = this;
        Object.keys(items).map(function(key){
            return items[key];
        }).sort(function(a, b){
            return b.touch - a.touch;
        }).forEach(function(item: RPGMakerMV.ImageCacheItem){
            //if(sizeLeft > 0 || this._mustBeHeld(item)){
            if(sizeLeft > 0 || _this._mustBeHeld(item)){
                var bitmap = item.bitmap;
                sizeLeft -= bitmap.width * bitmap.height;
            }else{
                delete items[item.key];
            }
        }.bind(this));
    };

    _mustBeHeld(item: RPGMakerMV.ImageCacheItem): boolean{
        // request only is weak so It's purgeable
        if(item.bitmap.isRequestOnly()) return false;
        // reserved item must be held
        if(item.reservationId) return true;
        // not ready bitmap must be held (because of checking isReady())
        if(!item.bitmap.isReady()) return true;
        // then the item may purgeable
        return false;        
    };


    isReady(): boolean{
        var items = this._items;
        return !Object.keys(items).some(function(key){
            return !items[key].bitmap.isRequestOnly() && !items[key].bitmap.isReady();
        });
    };

    getErrorBitmap(): Bitmap{
        var items = this._items;
        var bitmap = null;
        if(Object.keys(items).some(function(key){
                if(items[key].bitmap.isError()){
                    bitmap = items[key].bitmap;
                    return true;
                }
                return false;
            })) {
            return bitmap;
        }
    
        return null;        
    };
}



class RequestQueue {
    constructor(){
        this.initialize.apply(this, arguments);
    };
    _queue:any;
    initialize(): void{
        this._queue = [];
    };
    enqueue(key: string, value: Bitmap): void{
        this._queue.push({
            key: key,
            value: value,
        });
    };
    update(): void{
        if(this._queue.length === 0) return;

        var top = this._queue[0];
        if(top.value.isRequestReady()){
            this._queue.shift();
            if(this._queue.length !== 0){
                this._queue[0].value.startRequest();
            }
        }else{
            top.value.startRequest();
        }
    };
    raisePriority(key: string): void{
        for(var n = 0; n < this._queue.length; n++){
            var item = this._queue[n];
            if(item.key === key){
                this._queue.splice(n, 1);
                this._queue.unshift(item);
                break;
            }
        }
    };
    clear(): void{
        this._queue.splice(0);
    };
}

//-----------------------------------------------------------------------------
/**
 * The point class.
 *
 * @class Point
 * @constructor
 * @param {Number} x The x coordinate
 * @param {Number} y The y coordinate
 */
class Point extends PIXI.Point {
    constructor(x?: number, y?: number){
        super();
        this.x = x;
        this.y = y
        this.initialize.apply(this, arguments);
    };

    initialize(x?: number, y?: number): void{
        PIXI.Point.call(this, x, y);
    };
} 

/*Point.prototype = Object.create(PIXI.Point.prototype);
Point.prototype.constructor = Point;

Point.prototype.initialize = function(x, y) {
    PIXI.Point.call(this, x, y);
};*/

/**
 * The x coordinate.
 *
 * @property x
 * @type Number
 */

/**
 * The y coordinate.
 *
 * @property y
 * @type Number
 */

//-----------------------------------------------------------------------------
/**
 * The rectangle class.
 *
 * @class Rectangle
 * @constructor
 * @param {Number} x The x coordinate for the upper-left corner
 * @param {Number} y The y coordinate for the upper-left corner
 * @param {Number} width The width of the rectangle
 * @param {Number} height The height of the rectangle
 */
class Rectangle extends PIXI.Rectangle {
   
    constructor(x?: number, y?: number, width?: number, height?: number){
        super();
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        this.initialize.apply(this, arguments);

    };
    initialize(x?: number, y?: number, width?: number, height?: number): void{
        PIXI.Rectangle.call(this, x, y, width, height);
    };
   
    /**
     * @static
     * @property emptyRectangle
     * @type Rectangle
     * @private
     */
    static emptyRectangle: Rectangle = new Rectangle(0, 0, 0, 0);;
} 

/*Rectangle.prototype = Object.create(PIXI.Rectangle.prototype);
Rectangle.prototype.constructor = Rectangle;

Rectangle.prototype.initialize = function(x, y, width, height) {
    PIXI.Rectangle.call(this, x, y, width, height);
};*/




/**
 * The x coordinate for the upper-left corner.
 *
 * @property x
 * @type Number
 */

/**
* The y coordinate for the upper-left corner.
*
* @property y
* @type Number
*/

/**
* The width of the rectangle.
*
* @property width
* @type Number
*/

/**
* The height of the rectangle.
*
* @property height
* @type Number
*/

//-----------------------------------------------------------------------------
/**
* The basic object that represents an image.
*
* @class Bitmap
* @constructor
* @param {Number} width The width of the bitmap
* @param {Number} height The height of the bitmap
*/
class Bitmap {
    
    constructor(width?: number, height?: number){
        //this.width = width;
        //this.height = height;
        this.initialize.apply(this, arguments);
    };
    //for iOS. img consumes memory. so reuse it.
    static _reuseImages: HTMLImageElement[] = [];

    /**
     * Bitmap states(Bitmap._loadingState):
     *
     * none:
     * Empty Bitmap
     *
     * pending:
     * Url requested, but pending to load until startRequest called
     *
     * purged:
     * Url request completed and purged.
     *
     * requesting:
     * Requesting supplied URI now.
     *
     * requestCompleted:
     * Request completed
     *
     * decrypting:
     * requesting encrypted data from supplied URI or decrypting it.
     *
     * decryptCompleted:
     * Decrypt completed
     *
     * loaded:
     * loaded. isReady() === true, so It's usable.
     *
     * error:
     * error occurred
     *
     */
    _createCanvas(width?: number, height?: number): void{
        this.__canvas = this.__canvas || document.createElement('canvas');
        this.__context = this.__canvas.getContext('2d');
    
        this.__canvas.width = Math.max(width || 0, 1);
        this.__canvas.height = Math.max(height || 0, 1);
    
        if(this._image){
            var w = Math.max(this._image.width || 0, 1);
            var h = Math.max(this._image.height || 0, 1);
            this.__canvas.width = w;
            this.__canvas.height = h;
            this._createBaseTexture(this._canvas);
    
            this.__context.drawImage(this._image, 0, 0);
        }
    
        this._setDirty();
    };
    __canvas: HTMLCanvasElement;
    __context: CanvasRenderingContext2D;
    __baseTexture: PIXI.BaseTexture;
    _createBaseTexture(source: HTMLImageElement | HTMLCanvasElement | HTMLVideoElement): void{
        this.__baseTexture = new PIXI.BaseTexture(source);
        this.__baseTexture.mipmap = false;
        this.__baseTexture.width = source.width;
        this.__baseTexture.height = source.height;
    
        if (this._smooth) {
            this._baseTexture.scaleMode = PIXI.SCALE_MODES.LINEAR;
        } else {
            this._baseTexture.scaleMode = PIXI.SCALE_MODES.NEAREST;
        }
    };
    _clearImgInstance(): void{
        this._image.src = "";
        this._image.onload = null;
        this._image.onerror = null;
        this._errorListener = null;
        this._loadListener = null;
    
        Bitmap._reuseImages.push(this._image);
        this._image = null;
    };
    //TODO 自己添加
    _errorListener:any;
    _loadListener:any;

    //
    //We don't want to waste memory, so creating canvas is deferred.
    //
    //_canvas: HTMLCanvasElement;
    //_context: CanvasRenderingContext2D;
    //_baseTexture: PIXI.BaseTexture;
    public get _canvas(): HTMLCanvasElement {
        if (!this.__canvas)
        this._createCanvas();
        return this.__canvas;
    }

    public get _context(): CanvasRenderingContext2D {
        if (!this.__context)
        this._createCanvas();
        return this.__context;
    }

    public get _baseTexture(): PIXI.BaseTexture {
        if (!this.__baseTexture)
            this._createBaseTexture(this._image || this.__canvas);
        return this.__baseTexture;
    }



    _renewCanvas(): void{
        var newImage = this._image;
        if(newImage && this.__canvas && (this.__canvas.width < newImage.width || this.__canvas.height < newImage.height)){
            this._createCanvas();
        }
    };

    //TODO 自己添加
    _defer:any;
    _dirty:boolean;
    _loader:any;

    initialize(width?: number, height?: number): void{
        if(!this._defer){
            this._createCanvas(width, height);
        }
    
        this._image = null;
        this._url = '';
        this._paintOpacity = 255;
        this._smooth = false;
        this._loadListeners = [];
        this._loadingState = 'none';
        this._decodeAfterRequest = false;
    
        /**
         * Cache entry, for images. In all cases _url is the same as cacheEntry.key
         * @type CacheEntry
         */
        this.cacheEntry = null;
    
        /**
         * The face name of the font.
         *
         * @property fontFace
         * @type String
         */
        this.fontFace = 'GameFont';
    
        /**
         * The size of the font in pixels.
         *
         * @property fontSize
         * @type Number
         */
        this.fontSize = 28;
    
        /**
         * Whether the font is italic.
         *
         * @property fontItalic
         * @type Boolean
         */
        this.fontItalic = false;
    
        /**
         * The color of the text in CSS format.
         *
         * @property textColor
         * @type String
         */
        this.textColor = '#ffffff';
    
        /**
         * The color of the outline of the text in CSS format.
         *
         * @property outlineColor
         * @type String
         */
        this.outlineColor = 'rgba(0, 0, 0, 0.5)';
    
        /**
         * The width of the outline of the text.
         *
         * @property outlineWidth
         * @type Number
         */
        this.outlineWidth = 4;
    };
    _image: HTMLImageElement;
    _url: string;
    _paintOpacity: number;
    _smooth: boolean;
    _loadListeners: Function[];
    _loadingState: RPGMakerMV.BitmapLoadingState;
    _decodeAfterRequest: boolean;
    
    cacheEntry: CacheEntry;
  
    fontFace: string;
    
    fontSize: number;
     
    fontItalic: boolean;
   
    textColor: string;
    
    outlineColor: string;
    
    outlineWidth: number;
 
    /**
     * Loads a image file and returns a new bitmap object.
     *
     * @static
     * @method load
     * @param {String} url The image url of the texture
     * @return Bitmap
     */
    static load(url: string): Bitmap{
        var bitmap = Object.create(Bitmap.prototype);
        bitmap._defer = true;
        bitmap.initialize();
    
        bitmap._decodeAfterRequest = true;
        bitmap._requestImage(url);
    
        return bitmap;
    };

    /**
     * Takes a snapshot of the game screen and returns a new bitmap object.
     *
     * @static
     * @method snap
     * @param {Stage} stage The stage object
     * @return Bitmap
     */
    static snap(stage: Stage): Bitmap{
        var width = Graphics.width;
        var height = Graphics.height;
        var bitmap = new Bitmap(width, height);
        var context = bitmap._context;
        var renderTexture = PIXI.RenderTexture.create(width, height);
        if (stage) {
            Graphics._renderer.render(stage, renderTexture);
            stage.worldTransform.identity();
            var canvas = null;
            if (Graphics.isWebGL()) {
                canvas = Graphics._renderer.extract.canvas(renderTexture);
            } else {
                canvas = renderTexture.baseTexture._canvasRenderTarget.canvas;
            }
            context.drawImage(canvas, 0, 0);
        } else {
    
        }
        renderTexture.destroy({ destroyBase: true });
        bitmap._setDirty();
        return bitmap;
    };
 
    /**
     * Checks whether the bitmap is ready to render.
     *
     * @method isReady
     * @return {Boolean} True if the bitmap is ready to render
     */
    isReady(): boolean {
        return this._loadingState === 'loaded' || this._loadingState === 'none';
    };
     
    /**
     * Checks whether a loading error has occurred.
     *
     * @method isError
     * @return {Boolean} True if a loading error has occurred
     */
    isError(): boolean {
        return this._loadingState === 'error';
    };
    
    /**
     * touch the resource
     * @method touch
     */
    touch(): void {
        if (this.cacheEntry) {
            this.cacheEntry.touch();
        }
    };
 
    /**
     * [read-only] The url of the image file.
     *
     * @property url
     * @type String
     */
    //readonly url: string;
    public get url() :string {
        return this._url;
    }
    
    /**
     * [read-only] The base texture that holds the image.
     *
     * @property baseTexture
     * @type PIXI.BaseTexture
     */
    //readonly baseTexture: PIXI.BaseTexture;
    public get baseTexture() :PIXI.BaseTexture {
        return this._baseTexture;
    }
    
    

    /**
     * [read-only] The bitmap canvas.
     *
     * @property canvas
     * @type HTMLCanvasElement
     */
    //readonly canvas: HTMLCanvasElement;
    public get canvas() :HTMLCanvasElement {
        return this._canvas;
    }
  
    /**
     * [read-only] The 2d context of the bitmap canvas.
     *
     * @property context
     * @type CanvasRenderingContext2D
     */
    //readonly context: CanvasRenderingContext2D;
    public get context() :CanvasRenderingContext2D {
        return this._context;
    }
   
    /**
     * [read-only] The width of the bitmap.
     *
     * @property width
     * @type Number
     */
    //readonly width: number;
    public get width() :number {
        if(this.isReady()){
            return this._image? this._image.width: this._canvas.width;
        }
        return 0;
    }
 
    /**
     * [read-only] The height of the bitmap.
     *
     * @property height
     * @type Number
     */
    //readonly height: number;
    public get height() :number {
        if(this.isReady()){
            return this._image? this._image.height: this._canvas.height;
        }

        return 0;
    }
 
    /**
     * [read-only] The rectangle of the bitmap.
     *
     * @property rect
     * @type Rectangle
     */
    //readonly rect: Rectangle;
    public get rect() :Rectangle {
        return new Rectangle(0, 0, this.width, this.height);
    }
 
    /**
     * Whether the smooth scaling is applied.
     *
     * @property smooth
     * @type Boolean
     */
    //smooth: boolean;
    public get smooth() :boolean {
        return this._smooth;    
    }

    public set smooth(value:boolean) {
        if (this._smooth !== value) {
            this._smooth = value;
            if(this.__baseTexture){
                if (this._smooth) {
                    this._baseTexture.scaleMode = PIXI.SCALE_MODES.LINEAR;
                } else {
                    this._baseTexture.scaleMode = PIXI.SCALE_MODES.NEAREST;
                }
            }
        }
    }
    /**
     * The opacity of the drawing object in the range (0, 255).
     *
     * @property paintOpacity
     * @type Number
     */   
    //paintOpacity: number;
    public get paintOpacity(): number {
        return this._paintOpacity;
    }

    public set paintOpacity(value: number) {
        if (this._paintOpacity !== value) {
            this._paintOpacity = value;
            this._context.globalAlpha = this._paintOpacity / 255;
        }
    }

    /**
     * Resizes the bitmap.
     *
     * @method resize
     * @param {Number} width The new width of the bitmap
     * @param {Number} height The new height of the bitmap
     */
    resize(width: number, height: number): void{
        width = Math.max(width || 0, 1);
        height = Math.max(height || 0, 1);
        this._canvas.width = width;
        this._canvas.height = height;
        this._baseTexture.width = width;
        this._baseTexture.height = height;
    };
   /**
     * Performs a block transfer.
     *
     * @method blt
     * @param {Bitmap} source The bitmap to draw
     * @param {Number} sx The x coordinate in the source
     * @param {Number} sy The y coordinate in the source
     * @param {Number} sw The width of the source image
     * @param {Number} sh The height of the source image
     * @param {Number} dx The x coordinate in the destination
     * @param {Number} dy The y coordinate in the destination
     * @param {Number} [dw=sw] The width to draw the image in the destination
     * @param {Number} [dh=sh] The height to draw the image in the destination
     */ 
    blt(source: Bitmap, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw?: number, dh?: number): void{
        dw = dw || sw;
        dh = dh || sh;
        if (sx >= 0 && sy >= 0 && sw > 0 && sh > 0 && dw > 0 && dh > 0 &&
                sx + sw <= source.width && sy + sh <= source.height) {
            this._context.globalCompositeOperation = 'source-over';
            this._context.drawImage(source._canvas, sx, sy, sw, sh, dx, dy, dw, dh);
            this._setDirty();
        }
    };

    /**
     * Performs a block transfer, using assumption that original image was not modified (no hue)
     *
     * @method blt
     * @param {Bitmap} source The bitmap to draw
     * @param {Number} sx The x coordinate in the source
     * @param {Number} sy The y coordinate in the source
     * @param {Number} sw The width of the source image
     * @param {Number} sh The height of the source image
     * @param {Number} dx The x coordinate in the destination
     * @param {Number} dy The y coordinate in the destination
     * @param {Number} [dw=sw] The width to draw the image in the destination
     * @param {Number} [dh=sh] The height to draw the image in the destination
     */
    bltImage(source: Bitmap, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw?: number, dh?: number): void{
        dw = dw || sw;
        dh = dh || sh;
        if (sx >= 0 && sy >= 0 && sw > 0 && sh > 0 && dw > 0 && dh > 0 &&
            sx + sw <= source.width && sy + sh <= source.height) {
            this._context.globalCompositeOperation = 'source-over';
            this._context.drawImage(source._image, sx, sy, sw, sh, dx, dy, dw, dh);
            this._setDirty();
        }
    };

    /**
     * Returns pixel color at the specified point.
     *
     * @method getPixel
     * @param {Number} x The x coordinate of the pixel in the bitmap
     * @param {Number} y The y coordinate of the pixel in the bitmap
     * @return {String} The pixel color (hex format)
     */
    getPixel(x: number, y: number): string{
        var data = this._context.getImageData(x, y, 1, 1).data;
        var result = '#';
        for (var i = 0; i < 3; i++) {
            result += data[i].toString(16).padZero(2);
        }
        return result;
    };

    /**
     * Returns alpha pixel value at the specified point.
     *
     * @method getAlphaPixel
     * @param {Number} x The x coordinate of the pixel in the bitmap
     * @param {Number} y The y coordinate of the pixel in the bitmap
     * @return {String} The alpha value
     */   
    getAlphaPixel(x: number, y: number): string{
        var data = this._context.getImageData(x, y, 1, 1).data;
        //return data[3];
        //FIXME 更改如下
        return data[3]+'';
    };

    /**
     * Clears the specified rectangle.
     *
     * @method clearRect
     * @param {Number} x The x coordinate for the upper-left corner
     * @param {Number} y The y coordinate for the upper-left corner
     * @param {Number} width The width of the rectangle to clear
     * @param {Number} height The height of the rectangle to clear
     */
    clearRect(x: number, y: number, width: number, height: number): void{
        this._context.clearRect(x, y, width, height);
        this._setDirty();
    };
    /**
     * Clears the entire bitmap.
     *
     * @method clear
     */
    clear(): void{
        this.clearRect(0, 0, this.width, this.height);
    };

    /**
     * Fills the specified rectangle.
     *
     * @method fillRect
     * @param {Number} x The x coordinate for the upper-left corner
     * @param {Number} y The y coordinate for the upper-left corner
     * @param {Number} width The width of the rectangle to fill
     * @param {Number} height The height of the rectangle to fill
     * @param {String} color The color of the rectangle in CSS format
     */
    fillRect(x: number, y: number, width: number, height: number, color: string): void{
        var context = this._context;
        context.save();
        context.fillStyle = color;
        context.fillRect(x, y, width, height);
        context.restore();
        this._setDirty();
    };

    /**
     * Fills the entire bitmap.
     *
     * @method fillAll
     * @param {String} color The color of the rectangle in CSS format
     */
    fillAll(color: string): void{
        this.fillRect(0, 0, this.width, this.height, color);
    };
 
    /**
     * Draws the rectangle with a gradation.
     *
     * @method gradientFillRect
     * @param {Number} x The x coordinate for the upper-left corner
     * @param {Number} y The y coordinate for the upper-left corner
     * @param {Number} width The width of the rectangle to fill
     * @param {Number} height The height of the rectangle to fill
     * @param {String} color1 The gradient starting color
     * @param {String} color2 The gradient ending color
     * @param {Boolean} vertical Wether the gradient should be draw as vertical or not
     */
    gradientFillRect(x: number, y: number, width: number, height: number, color1: string, color2: string, vertical?: boolean): void{
        var context = this._context;
        var grad;
        if (vertical) {
            grad = context.createLinearGradient(x, y, x, y + height);
        } else {
            grad = context.createLinearGradient(x, y, x + width, y);
        }
        grad.addColorStop(0, color1);
        grad.addColorStop(1, color2);
        context.save();
        context.fillStyle = grad;
        context.fillRect(x, y, width, height);
        context.restore();
        this._setDirty();
    };
    /**
     * Draw a bitmap in the shape of a circle
     *
     * @method drawCircle
     * @param {Number} x The x coordinate based on the circle center
     * @param {Number} y The y coordinate based on the circle center
     * @param {Number} radius The radius of the circle
     * @param {String} color The color of the circle in CSS format
     */
    drawCircle(x: number, y: number, radius: number, color: string): void{
        var context = this._context;
        context.save();
        context.fillStyle = color;
        context.beginPath();
        context.arc(x, y, radius, 0, Math.PI * 2, false);
        context.fill();
        context.restore();
        this._setDirty();
    };
 
    /**
     * Draws the outline text to the bitmap.
     *
     * @method drawText
     * @param {String} text The text that will be drawn
     * @param {Number} x The x coordinate for the left of the text
     * @param {Number} y The y coordinate for the top of the text
     * @param {Number} maxWidth The maximum allowed width of the text
     * @param {Number} lineHeight The height of the text line
     * @param {String} align The alignment of the text
     */
    
    drawText(text: string, x: number, y: number, maxWidth?: number, lineHeight?: number, align?: CanvasTextAlign): void{

        //FIXME 将align改为CanvasTextAlign类型
        //drawText(text: string, x: number, y: number, maxWidth?: number, lineHeight?: number, align?: string): 
        
        
        // Note: Firefox has a bug with textBaseline: Bug 737852
        //       So we use 'alphabetic' here.
        if (text !== undefined) {
            var tx = x;
            var ty = y + lineHeight - (lineHeight - this.fontSize * 0.7) / 2;
            var context = this._context;
            var alpha = context.globalAlpha;
            maxWidth = maxWidth || 0xffffffff;
            if (align === 'center') {
                tx += maxWidth / 2;
            }
            if (align === 'right') {
                tx += maxWidth;
            }
            context.save();
            context.font = this._makeFontNameText();
            context.textAlign = align;
            context.textBaseline = 'alphabetic';
            context.globalAlpha = 1;
            this._drawTextOutline(text, tx, ty, maxWidth);
            context.globalAlpha = alpha;
            this._drawTextBody(text, tx, ty, maxWidth);
            context.restore();
            this._setDirty();
        }
    };
    drawSmallText(text: string, x: number, y: number, maxWidth?: number, lineHeight?: number, align?: string): void{return null};
 
    /**
     * Returns the width of the specified text.
     *
     * @method measureTextWidth
     * @param {String} text The text to be measured
     * @return {Number} The width of the text in pixels
     */
    measureTextWidth(text: string): number{
        var context = this._context;
        context.save();
        context.font = this._makeFontNameText();
        var width = context.measureText(text).width;
        context.restore();
        return width;
    };

    /**
     * Changes the color tone of the entire bitmap.
     *
     * @method adjustTone
     * @param {Number} r The red strength in the range (-255, 255)
     * @param {Number} g The green strength in the range (-255, 255)
     * @param {Number} b The blue strength in the range (-255, 255)
     */
    adjustTone(r: number, g: number, b: number): void {
        if ((r || g || b) && this.width > 0 && this.height > 0) {
            var context = this._context;
            var imageData = context.getImageData(0, 0, this.width, this.height);
            var pixels = imageData.data;
            for (var i = 0; i < pixels.length; i += 4) {
                pixels[i + 0] += r;
                pixels[i + 1] += g;
                pixels[i + 2] += b;
            }
            context.putImageData(imageData, 0, 0);
            this._setDirty();
        }
    };

    /**
     * Rotates the hue of the entire bitmap.
     *
     * @method rotateHue
     * @param {Number} offset The hue offset in 360 degrees
     */   
    rotateHue(offset: number): void {
        function rgbToHsl(r:number, g:number, b:number) {
            var cmin = Math.min(r, g, b);
            var cmax = Math.max(r, g, b);
            var h = 0;
            var s = 0;
            var l = (cmin + cmax) / 2;
            var delta = cmax - cmin;
    
            if (delta > 0) {
                if (r === cmax) {
                    h = 60 * (((g - b) / delta + 6) % 6);
                } else if (g === cmax) {
                    h = 60 * ((b - r) / delta + 2);
                } else {
                    h = 60 * ((r - g) / delta + 4);
                }
                s = delta / (255 - Math.abs(2 * l - 255));
            }
            return [h, s, l];
        }
    
        function hslToRgb(h:number, s:number, l:number) {
            var c = (255 - Math.abs(2 * l - 255)) * s;
            var x = c * (1 - Math.abs((h / 60) % 2 - 1));
            var m = l - c / 2;
            var cm = c + m;
            var xm = x + m;
    
            if (h < 60) {
                return [cm, xm, m];
            } else if (h < 120) {
                return [xm, cm, m];
            } else if (h < 180) {
                return [m, cm, xm];
            } else if (h < 240) {
                return [m, xm, cm];
            } else if (h < 300) {
                return [xm, m, cm];
            } else {
                return [cm, m, xm];
            }
        }
    
        if (offset && this.width > 0 && this.height > 0) {
            offset = ((offset % 360) + 360) % 360;
            var context = this._context;
            var imageData = context.getImageData(0, 0, this.width, this.height);
            var pixels = imageData.data;
            for (var i = 0; i < pixels.length; i += 4) {
                var hsl = rgbToHsl(pixels[i + 0], pixels[i + 1], pixels[i + 2]);
                var h = (hsl[0] + offset) % 360;
                var s = hsl[1];
                var l = hsl[2];
                var rgb = hslToRgb(h, s, l);
                pixels[i + 0] = rgb[0];
                pixels[i + 1] = rgb[1];
                pixels[i + 2] = rgb[2];
            }
            context.putImageData(imageData, 0, 0);
            this._setDirty();
        }
    };
    /**
     * Applies a blur effect to the bitmap.
     *
     * @method blur
     */
    blur(): void {
        for (var i = 0; i < 2; i++) {
            var w = this.width;
            var h = this.height;
            var canvas = this._canvas;
            var context = this._context;
            var tempCanvas = document.createElement('canvas');
            var tempContext = tempCanvas.getContext('2d');
            tempCanvas.width = w + 2;
            tempCanvas.height = h + 2;
            tempContext.drawImage(canvas, 0, 0, w, h, 1, 1, w, h);
            tempContext.drawImage(canvas, 0, 0, w, 1, 1, 0, w, 1);
            tempContext.drawImage(canvas, 0, 0, 1, h, 0, 1, 1, h);
            tempContext.drawImage(canvas, 0, h - 1, w, 1, 1, h + 1, w, 1);
            tempContext.drawImage(canvas, w - 1, 0, 1, h, w + 1, 1, 1, h);
            context.save();
            context.fillStyle = 'black';
            context.fillRect(0, 0, w, h);
            context.globalCompositeOperation = 'lighter';
            context.globalAlpha = 1 / 9;
            for (var y = 0; y < 3; y++) {
                for (var x = 0; x < 3; x++) {
                    context.drawImage(tempCanvas, x, y, w, h, 0, 0, w, h);
                }
            }
            context.restore();
        }
        this._setDirty();
    };
    /**
     * Add a callback function that will be called when the bitmap is loaded.
     *
     * @method addLoadListener
     * @param {Function} listner The callback function
     */
    addLoadListener(listner: Function): void{
        if (!this.isReady()) {
            this._loadListeners.push(listner);
        } else {
            listner(this);
        }
    };
    /**
     * @method _makeFontNameText
     * @private
     */
    _makeFontNameText(): string{
        return (this.fontItalic ? 'Italic ' : '') +
                this.fontSize + 'px ' + this.fontFace;
    };

    /**
     * @method _drawTextOutline
     * @param {String} text
     * @param {Number} tx
     * @param {Number} ty
     * @param {Number} maxWidth
     * @private
     */
    _drawTextOutline(text: string, tx: number, ty: number, maxWidth: number): void{
        var context = this._context;
        context.strokeStyle = this.outlineColor;
        context.lineWidth = this.outlineWidth;
        context.lineJoin = 'round';
        context.strokeText(text, tx, ty, maxWidth);
    };
    /**
     * @method _drawTextBody
     * @param {String} text
     * @param {Number} tx
     * @param {Number} ty
     * @param {Number} maxWidth
     * @private
     */   
    _drawTextBody(text: string, tx: number, ty: number, maxWidth: number): void{
        var context = this._context;
        context.fillStyle = this.textColor;
        context.fillText(text, tx, ty, maxWidth);
    };
    /**
     * @method _onLoad
     * @private
     */
    _onLoad(): void{
        this._image.removeEventListener('load', this._loadListener);
        this._image.removeEventListener('error', this._errorListener);
    
        this._renewCanvas();
    
        switch(this._loadingState){
            case 'requesting':
                this._loadingState = 'requestCompleted';
                if(this._decodeAfterRequest){
                    this.decode();
                }else{
                    this._loadingState = 'purged';
                    this._clearImgInstance();
                }
                break;
    
            case 'decrypting':
                window.URL.revokeObjectURL(this._image.src);
                this._loadingState = 'decryptCompleted';
                if(this._decodeAfterRequest){
                    this.decode();
                }else{
                    this._loadingState = 'purged';
                    this._clearImgInstance();
                }
                break;
        }
    };
    decode(): void{
        switch(this._loadingState){
            case 'requestCompleted': case 'decryptCompleted':
                this._loadingState = 'loaded';
    
                if(!this.__canvas) this._createBaseTexture(this._image);
                this._setDirty();
                this._callLoadListeners();
                break;
    
            case 'requesting': case 'decrypting':
                this._decodeAfterRequest = true;
                if (!this._loader) {
                    //FIXME 修改
                    //this._loader = ResourceHandler.createLoader(this._url, this._requestImage.bind(this, this._url), this._onError.bind(this));
                    this._loader = ResourceHandler.createLoader(this._url, this._requestImage.bind(this, this._url), this._onError.bind(this), null);
                    this._image.removeEventListener('error', this._errorListener);
                    this._image.addEventListener('error', this._errorListener = this._loader);
                }
                break;
    
            case 'pending': case 'purged': case 'error':
                this._decodeAfterRequest = true;
                this._requestImage(this._url);
                break;
        }
    };
    /**
     * @method _callLoadListeners
     * @private
     */
    _callLoadListeners(): void {
        while (this._loadListeners.length > 0) {
            var listener = this._loadListeners.shift();
            listener(this);
        }
    };
    /**
     * @method _onError
     * @private
     */
    _onError(): void {
        this._image.removeEventListener('load', this._loadListener);
        this._image.removeEventListener('error', this._errorListener);
        this._loadingState = 'error';
    };
    /**
     * @method _setDirty
     * @private
     */
    _setDirty(): void {
        this._dirty = true;
    };

    /**
     * updates texture is bitmap was dirty
     * @method checkDirty
     */
    checkDirty(): void {
        if (this._dirty) {
            this._baseTexture.update();
            this._dirty = false;
        }
    };

    static request(url: string): Bitmap{
        var bitmap = Object.create(Bitmap.prototype);
        bitmap._defer = true;
        bitmap.initialize();
    
        bitmap._url = url;
        bitmap._loadingState = 'pending';
    
        return bitmap;
    };
    _requestImage(url: string): void{
        if(Bitmap._reuseImages.length !== 0){
            this._image = Bitmap._reuseImages.pop();
        }else{
            this._image = new Image();
        }
    
        if (this._decodeAfterRequest && !this._loader) {
            //this._loader = ResourceHandler.createLoader(url, this._requestImage.bind(this, url), this._onError.bind(this));
       
            this._loader = ResourceHandler.createLoader(url, this._requestImage.bind(this, url), this._onError.bind(this),null);
       
        }
    
        this._image = new Image();
        this._url = url;
        this._loadingState = 'requesting';
    
        if(!Decrypter.checkImgIgnore(url) && Decrypter.hasEncryptedImages) {
            this._loadingState = 'decrypting';
            Decrypter.decryptImg(url, this);
        } else {
            this._image.src = url;
    
            this._image.addEventListener('load', this._loadListener = Bitmap.prototype._onLoad.bind(this));
            this._image.addEventListener('error', this._errorListener = this._loader || Bitmap.prototype._onError.bind(this));
        }
    };
    isRequestOnly(): boolean{
        return !(this._decodeAfterRequest || this.isReady());
    };
    isRequestReady(): boolean{
        return this._loadingState !== 'pending' &&
            this._loadingState !== 'requesting' &&
            this._loadingState !== 'decrypting';
    };
    startRequest(): void{
        if(this._loadingState === 'pending'){
            this._decodeAfterRequest = false;
            this._requestImage(this._url);
        }
    };
    minFontSize: number;
    drawSmallTextBitmap: Bitmap;
}

//
//We don't want to waste memory, so creating canvas is deferred.
//
/*Object.defineProperties(Bitmap.prototype, {
    _canvas: {
        get: function(){
            if(!this.__canvas)this._createCanvas();
            return this.__canvas;
        }
    },
    _context: {
        get: function(){
            if(!this.__context)this._createCanvas();
            return this.__context;
        }
    },

    _baseTexture: {
        get: function(){
            if(!this.__baseTexture) this._createBaseTexture(this._image || this.__canvas);
            return this.__baseTexture;
        }
    }
});*/
//-----------------------------------------------------------------------------
/**
 * The static class that carries out graphics processing.
 *
 * @class Graphics
 */
class Graphics {
    private constructor(){
        throw new Error('This is a static class');
    }

    static _cssFontLoading: boolean=  document.fonts && document.fonts.ready;
    static _fontLoaded: any = null; //默认为null FontFaceSet
    static _videoVolume: number = 1;

    /**
     * Initializes the graphics system.
     *
     * @static
     * @method initialize
     * @param {Number} width The width of the game screen
     * @param {Number} height The height of the game screen
     * @param {String} type The type of the renderer.
     *                 'canvas', 'webgl', or 'auto'.
     */
    static initialize(width?: number, height?: number, type?: string): void{
        this._width = width || 800;
        this._height = height || 600;
        this._rendererType = type || 'auto';
        this._boxWidth = this._width;
        this._boxHeight = this._height;
    
        this._scale = 1;
        this._realScale = 1;
    
        this._errorShowed = false;
        this._errorPrinter = null;
        this._canvas = null;
        this._video = null;
        this._videoUnlocked = false;
        this._videoLoading = false;
        this._upperCanvas = null;
        this._renderer = null;
        this._fpsMeter = null;
        this._modeBox = null;
        this._skipCount = 0;
        this._maxSkip = 3;
        this._rendered = false;
        this._loadingImage = null;
        this._loadingCount = 0;
        this._fpsMeterToggled = false;
        this._stretchEnabled = this._defaultStretchMode();
    
        this._canUseDifferenceBlend = false;
        this._canUseSaturationBlend = false;
        this._hiddenCanvas = null;
    
        this._testCanvasBlendModes();
        this._modifyExistingElements();
        this._updateRealScale();
        this._createAllElements();
        this._disableTextSelection();
        this._disableContextMenu();
        this._setupEventHandlers();
        this._setupCssFontLoading();
    };
    static _width: number;
    static _height: number;
    static _rendererType: string;
    static _boxWidth: number;
    static _boxHeight: number;
    static _scale: number;
    static _realScale: number;
    static _errorShowed: boolean;
    static _errorPrinter: HTMLParagraphElement;
    static _canvas: HTMLCanvasElement;
    //static _video: HTMLVideoElement;
    //FIXME
    static _video: any;
    static _videoUnlocked: boolean;
    static _videoLoading: boolean;
    static _upperCanvas: HTMLCanvasElement;
    static _renderer: PIXI.SystemRenderer;
    static _fpsMeter: any;
    static _modeBox: HTMLDivElement;
    static _skipCount: number;
    static _maxSkip: number;
    static _rendered: boolean;
    static _loadingImage: HTMLImageElement;
    static _loadingCount: number;
    static _fpsMeterToggled: boolean;
    static _stretchEnabled: boolean;
    static _canUseDifferenceBlend: boolean;
    static _canUseSaturationBlend: boolean;
    static _hiddenCanvas: HTMLCanvasElement;

    static _setupCssFontLoading(): void{
        if(Graphics._cssFontLoading){
            document.fonts.ready.then(function(fonts:any){
                Graphics._fontLoaded = fonts;
            }).catch(function(error:ErrorEvent){
                SceneManager.onError(error);
            });
        }
    };
    static canUseCssFontLoading(): boolean{
        return !!this._cssFontLoading;
    };

    /**
     * The total frame count of the game screen.
     *
     * @static
     * @property frameCount
     * @type Number
     */
    static frameCount: number = 0;

    /**
     * The alias of PIXI.blendModes.NORMAL.
     *
     * @static
     * @property BLEND_NORMAL
     * @type Number
     * @final
     */
    static BLEND_NORMAL: 0 = 0;

    /**
     * The alias of PIXI.blendModes.ADD.
     *
     * @static
     * @property BLEND_ADD
     * @type Number
     * @final
     */
    static BLEND_ADD: 1 = 1;

    /**
     * The alias of PIXI.blendModes.MULTIPLY.
     *
     * @static
     * @property BLEND_MULTIPLY
     * @type Number
     * @final
     */
    static BLEND_MULTIPLY: 2 = 2;

    /**
     * The alias of PIXI.blendModes.SCREEN.
     *
     * @static
     * @property BLEND_SCREEN
     * @type Number
     * @final
     */
    static BLEND_SCREEN: 3 = 3;

    /**
     * Marks the beginning of each frame for FPSMeter.
     *
     * @static
     * @method tickStart
     */
    static tickStart(): void {
        if (this._fpsMeter) {
            this._fpsMeter.tickStart();
        }
    };
    /**
     * Marks the end of each frame for FPSMeter.
     *
     * @static
     * @method tickEnd
     */
    static tickEnd(): void {
        if (this._fpsMeter && this._rendered) {
            this._fpsMeter.tick();
        }
    };
    /**
     * Renders the stage to the game screen.
     *
     * @static
     * @method render
     * @param {Stage} stage The stage object to be rendered
     */
    static render(stage: Stage): void{
        if (this._skipCount === 0) {
            var startTime = Date.now();
            if (stage) {
                this._renderer.render(stage);
                if (this._renderer.gl && this._renderer.gl.flush) {
                    this._renderer.gl.flush();
                }
            }
            var endTime = Date.now();
            var elapsed = endTime - startTime;
            this._skipCount = Math.min(Math.floor(elapsed / 15), this._maxSkip);
            this._rendered = true;
        } else {
            this._skipCount--;
            this._rendered = false;
        }
        this.frameCount++;
    };

    /**
     * Checks whether the renderer type is WebGL.
     *
     * @static
     * @method isWebGL
     * @return {Boolean} True if the renderer type is WebGL
     */
    static isWebGL(): boolean {
        return this._renderer && this._renderer.type === PIXI.RENDERER_TYPE.WEBGL;
    };

    /**
     * Checks whether the current browser supports WebGL.
     *
     * @static
     * @method hasWebGL
     * @return {Boolean} True if the current browser supports WebGL.
     */
    static hasWebGL(): boolean {
        try {
            var canvas = document.createElement('canvas');
            return !!(canvas.getContext('webgl') || canvas.getContext('experimental-webgl'));
        } catch (e) {
            return false;
        }
    };

    /**
     * Checks whether the canvas blend mode 'difference' is supported.
     *
     * @static
     * @method canUseDifferenceBlend
     * @return {Boolean} True if the canvas blend mode 'difference' is supported
     */
    static canUseDifferenceBlend(): boolean {
        return this._canUseDifferenceBlend;
    };

    /**
     * Checks whether the canvas blend mode 'saturation' is supported.
     *
     * @static
     * @method canUseSaturationBlend
     * @return {Boolean} True if the canvas blend mode 'saturation' is supported
     */
    static canUseSaturationBlend(): boolean {
        return this._canUseSaturationBlend;
    };

    /**
     * Sets the source of the "Now Loading" image.
     *
     * @static
     * @method setLoadingImage
     */
    static setLoadingImage(src: string): void {
        this._loadingImage = new Image();
        this._loadingImage.src = src;
    };

    /**
     * Initializes the counter for displaying the "Now Loading" image.
     *
     * @static
     * @method startLoading
     */
    static startLoading(): void {
        this._loadingCount = 0;
    };
    /**
     * Increments the loading counter and displays the "Now Loading" image if necessary.
     *
     * @static
     * @method updateLoading
     */  
    static updateLoading(): void{
        this._loadingCount++;
        this._paintUpperCanvas();
        this._upperCanvas.style.opacity = 1;
    };

    /**
     * Erases the "Now Loading" image.
     *
     * @static
     * @method endLoading
     */
    static endLoading(): void{
        this._clearUpperCanvas();
        this._upperCanvas.style.opacity = 0;
    };
    /**
     * Displays the loading error text to the screen.
     *
     * @static
     * @method printLoadingError
     * @param {String} url The url of the resource failed to load
     */
    static printLoadingError(url: string): void {
        if (this._errorPrinter && !this._errorShowed) {
            this._errorPrinter.innerHTML = this._makeErrorHtml('Loading Error', 'Failed to load: ' + url);
            var button = document.createElement('button');
            button.innerHTML = 'Retry';
            button.style.fontSize = '24px';
            button.style.color = '#ffffff';
            button.style.backgroundColor = '#000000';
            button.onmousedown = button.ontouchstart = function(event) {
                ResourceHandler.retry();
                event.stopPropagation();
            };
            this._errorPrinter.appendChild(button);
            this._loadingCount = -Infinity;
        }
    };
    /**
     * Erases the loading error text.
     *
     * @static
     * @method eraseLoadingError
     */
    static eraseLoadingError(): void {
        if (this._errorPrinter && !this._errorShowed) {
            this._errorPrinter.innerHTML = '';
            this.startLoading();
        }
    };
    /**
     * Displays the error text to the screen.
     *
     * @static
     * @method printError
     * @param {String} name The name of the error
     * @param {String} message The message of the error
     */
    static printError(name: string, message: string|any): void {
        this._errorShowed = true;
        if (this._errorPrinter) {
            this._errorPrinter.innerHTML = this._makeErrorHtml(name, message);
        }
        this._applyCanvasFilter();
        this._clearUpperCanvas();
    };

    /**
     * Shows the FPSMeter element.
     *
     * @static
     * @method showFps
     */
    static showFps(): void {
        if (this._fpsMeter) {
            this._fpsMeter.show();
            this._modeBox.style.opacity = 1;
        }
    };

    /**
     * Hides the FPSMeter element.
     *
     * @static
     * @method hideFps
     */
    static hideFps(): void {
        if (this._fpsMeter) {
            this._fpsMeter.hide();
            this._modeBox.style.opacity = 0;
        }
    };

    /**
     * Loads a font file.
     *
     * @static
     * @method loadFont
     * @param {String} name The face name of the font
     * @param {String} url The url of the font file
     */
    static loadFont(name: string, url: string): void  {
        var style = document.createElement('style');
        var head = document.getElementsByTagName('head');
        var rule = '@font-face { font-family: "' + name + '"; src: url("' + url + '"); }';
        style.type = 'text/css';
        head.item(0).appendChild(style);
        style.sheet.insertRule(rule, 0);
        this._createFontLoader(name);
    };

    /**
     * Checks whether the font file is loaded.
     *
     * @static
     * @method isFontLoaded
     * @param {String} name The face name of the font
     * @return {Boolean} True if the font file is loaded
     */    
    static isFontLoaded(name: string): boolean {
        if (Graphics._cssFontLoading) {
            if(Graphics._fontLoaded){
                return Graphics._fontLoaded.check('10px "'+name+'"');
            }
    
            return false;
        } else {
            if (!this._hiddenCanvas) {
                this._hiddenCanvas = document.createElement('canvas');
            }
            var context = this._hiddenCanvas.getContext('2d');
            var text = 'abcdefghijklmnopqrstuvwxyz';
            var width1, width2;
            context.font = '40px ' + name + ', sans-serif';
            width1 = context.measureText(text).width;
            context.font = '40px sans-serif';
            width2 = context.measureText(text).width;
            return width1 !== width2;
        }
    };

    /**
     * Starts playback of a video.
     *
     * @static
     * @method playVideo
     * @param {String} src
     */
    static playVideo(src: string): void {
        //this._videoLoader = ResourceHandler.createLoader(null, this._playVideo.bind(this, src), this._onVideoError.bind(this));
        //FIXME 修改参数数量
        this._videoLoader = ResourceHandler.createLoader(null, this._playVideo.bind(this, src), this._onVideoError.bind(this), null); 
        this._playVideo(src);
    };
    //FIXME 新增
    static _videoLoader:any;

    /**
     * @static
     * @method _playVideo
     * @param {String} src
     * @private
     */
    static _playVideo(src: string): void {
        this._video.src = src;
        this._video.onloadeddata = this._onVideoLoad.bind(this);
        this._video.onerror = this._videoLoader;
        this._video.onended = this._onVideoEnd.bind(this);
        this._video.load();
        this._videoLoading = true;
    };

    /**
     * Checks whether the video is playing.
     *
     * @static
     * @method isVideoPlaying
     * @return {Boolean} True if the video is playing
     */
    static isVideoPlaying(): boolean {
        return this._videoLoading || this._isVideoVisible();
    };

    /**
     * Checks whether the browser can play the specified video type.
     *
     * @static
     * @method canPlayVideoType
     * @param {String} type The video type to test support for
     * @return {Boolean} True if the browser can play the specified video type
     */
    static canPlayVideoType(type: string): boolean {
        return this._video && this._video.canPlayType(type);
    };

    /**
     * Sets volume of a video.
     *
     * @static
     * @method setVideoVolume
     * @param {Number} value
     */
    static setVideoVolume(value: number): void {
        this._videoVolume = value;
        if (this._video) {
            this._video.volume = this._videoVolume;
        }
    };

    /**
     * Converts an x coordinate on the page to the corresponding
     * x coordinate on the canvas area.
     *
     * @static
     * @method pageToCanvasX
     * @param {Number} x The x coordinate on the page to be converted
     * @return {Number} The x coordinate on the canvas area
     */
    static pageToCanvasX(x: number): number {
        if (this._canvas) {
            var left = this._canvas.offsetLeft;
            return Math.round((x - left) / this._realScale);
        } else {
            return 0;
        }
    };

    /**
     * Converts a y coordinate on the page to the corresponding
     * y coordinate on the canvas area.
     *
     * @static
     * @method pageToCanvasY
     * @param {Number} y The y coordinate on the page to be converted
     * @return {Number} The y coordinate on the canvas area
     */
    static pageToCanvasY(y: number): number {
        if (this._canvas) {
            var top = this._canvas.offsetTop;
            return Math.round((y - top) / this._realScale);
        } else {
            return 0;
        }
    };

    /**
     * Checks whether the specified point is inside the game canvas area.
     *
     * @static
     * @method isInsideCanvas
     * @param {Number} x The x coordinate on the canvas area
     * @param {Number} y The y coordinate on the canvas area
     * @return {Boolean} True if the specified point is inside the game canvas area
     */
    static isInsideCanvas(x: number, y: number): boolean {
        return (x >= 0 && x < this._width && y >= 0 && y < this._height);
    };

    /**
     * Calls pixi.js garbage collector
     */
    static callGC(): void {
        if (Graphics.isWebGL()) {
            Graphics._renderer.textureGC.run();
        }
    };

    /**
     * The width of the game screen.
     *
     * @static
     * @property width
     * @type Number
     */
    
    public static get width() {
        return this._width;
    }

    public static set width(value: number) {
        if (this._width !== value) {
            this._width = value;
            this._updateAllElements();
        }
    }

    /**
     * The height of the game screen.
     *
     * @static
     * @property height
     * @type Number
     */
    public static get height() {
        return this._height;
    }

    public static set height(value: number) {
        if (this._height !== value) {
            this._height = value;
            this._updateAllElements();
        }
    }

    /**
     * The width of the window display area.
     *
     * @static
     * @property boxWidth
     * @type Number
     */
    public static get boxWidth() {
        return this._boxWidth;
    }

    public static set boxWidth(value: number) {
        this._boxWidth = value;
    }
    
    /**
     * The height of the window display area.
     *
     * @static
     * @property boxHeight
     * @type Number
     */
    public static get boxHeight() {
        return this._boxHeight;
    }

    public static set boxHeight(value: number) {
        this._boxHeight = value;
    }

    /**
     * The zoom scale of the game screen.
     *
     * @static
     * @property scale
     * @type Number
     */
    public static get scale() {
        return this._scale;
    }

    public static set scale(value: number) {
        if (this._scale !== value) {
            this._scale = value;
            this._updateAllElements();
        }
    }
    
    /**
     * @static
     * @method _createAllElements
     * @private
     */
    static _createAllElements(): void {
        this._createErrorPrinter();
        this._createCanvas();
        this._createVideo();
        this._createUpperCanvas();
        this._createRenderer();
        this._createFPSMeter();
        this._createModeBox();
        this._createGameFontLoader();
    };

    /**
     * @static
     * @method _updateAllElements
     * @private
     */    
    static _updateAllElements(): void {
        this._updateRealScale();
        this._updateErrorPrinter();
        this._updateCanvas();
        this._updateVideo();
        this._updateUpperCanvas();
        this._updateRenderer();
        this._paintUpperCanvas();
    };

    /**
     * @static
     * @method _updateRealScale
     * @private
     */
    static _updateRealScale(): void {
        if (this._stretchEnabled) {
            var h = window.innerWidth / this._width;
            var v = window.innerHeight / this._height;
            if (h >= 1 && h - 0.01 <= 1) h = 1;
            if (v >= 1 && v - 0.01 <= 1) v = 1;
            this._realScale = Math.min(h, v);
        } else {
            this._realScale = this._scale;
        }
    };

    /**
     * @static
     * @method _makeErrorHtml
     * @param {String} name
     * @param {String} message
     * @return {String}
     * @private
     */
    static _makeErrorHtml(name: string, message: string): string {
        return ('<font color="yellow"><b>' + name + '</b></font><br>' +
                '<font color="white">' + message + '</font><br>');
    };

    /**
     * @static
     * @method _defaultStretchMode
     * @private
     */
    static _defaultStretchMode(): boolean {
        return Utils.isNwjs() || Utils.isMobileDevice();
    };

    /**
     * @static
     * @method _testCanvasBlendModes
     * @private
     */
    static _testCanvasBlendModes(): void {
        var canvas, context, imageData1, imageData2;
        canvas = document.createElement('canvas');
        canvas.width = 1;
        canvas.height = 1;
        context = canvas.getContext('2d');
        context.globalCompositeOperation = 'source-over';
        context.fillStyle = 'white';
        context.fillRect(0, 0, 1, 1);
        context.globalCompositeOperation = 'difference';
        context.fillStyle = 'white';
        context.fillRect(0, 0, 1, 1);
        imageData1 = context.getImageData(0, 0, 1, 1);
        context.globalCompositeOperation = 'source-over';
        context.fillStyle = 'black';
        context.fillRect(0, 0, 1, 1);
        context.globalCompositeOperation = 'saturation';
        context.fillStyle = 'white';
        context.fillRect(0, 0, 1, 1);
        imageData2 = context.getImageData(0, 0, 1, 1);
        this._canUseDifferenceBlend = imageData1.data[0] === 0;
        this._canUseSaturationBlend = imageData2.data[0] === 0;
    };

    /**
     * @static
     * @method _modifyExistingElements
     * @private
     */
    static _modifyExistingElements(): void {
        var elements = document.getElementsByTagName('*');
        for (var i = 0; i < elements.length; i++) {
            if (elements[i].style.zIndex > 0) {
                elements[i].style.zIndex = 0;
            }
        }
    };

    /**
     * @static
     * @method _createErrorPrinter
     * @private
     */
    static _createErrorPrinter(): void {
        this._errorPrinter = document.createElement('p');
        this._errorPrinter.id = 'ErrorPrinter';
        this._updateErrorPrinter();
        document.body.appendChild(this._errorPrinter);
    };

    /**
     * @static
     * @method _updateErrorPrinter
     * @private
     */
    static _updateErrorPrinter(): void {
        this._errorPrinter.width = this._width * 0.9;
        this._errorPrinter.height = 40;
        this._errorPrinter.style.textAlign = 'center';
        this._errorPrinter.style.textShadow = '1px 1px 3px #000';
        this._errorPrinter.style.fontSize = '20px';
        this._errorPrinter.style.zIndex = 99;
        this._centerElement(this._errorPrinter);
    };

    /**
     * @static
     * @method _createCanvas
     * @private
     */
    static _createCanvas(): void {
        this._canvas = document.createElement('canvas');
        this._canvas.id = 'GameCanvas';
        this._updateCanvas();
        document.body.appendChild(this._canvas);
    };

    /**
     * @static
     * @method _updateCanvas
     * @private
     */
    static _updateCanvas(): void {
        this._canvas.width = this._width;
        this._canvas.height = this._height;
        this._canvas.style.zIndex = 1;
        this._centerElement(this._canvas);
    };

    /**
     * @static
     * @method _createVideo
     * @private
     */
    static _createVideo(): void {
        this._video = document.createElement('video');
        this._video.id = 'GameVideo';
        this._video.style.opacity = 0;
        this._video.setAttribute('playsinline', '');
        this._video.volume = this._videoVolume;
        this._updateVideo();
        makeVideoPlayableInline(this._video);
        document.body.appendChild(this._video);
    };

    /**
     * @static
     * @method _updateVideo
     * @private
     */
    static _updateVideo(): void {
        this._video.width = this._width;
        this._video.height = this._height;
        this._video.style.zIndex = 2;
        this._centerElement(this._video);
    };

    /**
     * @static
     * @method _createUpperCanvas
     * @private
     */
    static _createUpperCanvas(): void {
        this._upperCanvas = document.createElement('canvas');
        this._upperCanvas.id = 'UpperCanvas';
        this._updateUpperCanvas();
        document.body.appendChild(this._upperCanvas);
    };

    /**
     * @static
     * @method _updateUpperCanvas
     * @private
     */    
    static _updateUpperCanvas(): void {
        this._upperCanvas.width = this._width;
        this._upperCanvas.height = this._height;
        this._upperCanvas.style.zIndex = 3;
        this._centerElement(this._upperCanvas);
    };

    /**
     * @static
     * @method _clearUpperCanvas
     * @private
     */
    static _clearUpperCanvas(): void {
        var context = this._upperCanvas.getContext('2d');
        context.clearRect(0, 0, this._width, this._height);
    };

    /**
     * @static
     * @method _paintUpperCanvas
     * @private
     */
    static _paintUpperCanvas(): void {
        this._clearUpperCanvas();
        if (this._loadingImage && this._loadingCount >= 20) {
            var context = this._upperCanvas.getContext('2d');
            var dx = (this._width - this._loadingImage.width) / 2;
            var dy = (this._height - this._loadingImage.height) / 2;
            var alpha = ((this._loadingCount - 20) / 30).clamp(0, 1);
            context.save();
            context.globalAlpha = alpha;
            context.drawImage(this._loadingImage, dx, dy);
            context.restore();
        }
    };

    /**
     * @static
     * @method _createRenderer
     * @private
     */
    static _createRenderer(): void {
        PIXI.dontSayHello = true;
        var width = this._width;
        var height = this._height;
        var options = { view: this._canvas };
        try {
            switch (this._rendererType) {
            case 'canvas':
                this._renderer = new PIXI.CanvasRenderer(width, height, options);
                break;
            case 'webgl':
                this._renderer = new PIXI.WebGLRenderer(width, height, options);
                break;
            default:
                this._renderer = PIXI.autoDetectRenderer(width, height, options);
                break;
            }
    
            if(this._renderer && this._renderer.textureGC)
                this._renderer.textureGC.maxIdle = 1;
    
        } catch (e) {
            this._renderer = null;
        }
    };

    /**
     * @static
     * @method _updateRenderer
     * @private
     */
    static _updateRenderer(): void {
        if (this._renderer) {
            this._renderer.resize(this._width, this._height);
        }
    };

    /**
     * @static
     * @method _createFPSMeter
     * @private
     */
    static _createFPSMeter(): void {
        var options:FPSMeterOptions = { graph: 1, decimals: 0, theme: 'transparent', toggleOn: null };
        this._fpsMeter = new FPSMeter(options);
        this._fpsMeter.hide();
    };

    /**
     * @static
     * @method _createModeBox
     * @private
     */
    static _createModeBox(): void {
        var box = document.createElement('div');
        box.id = 'modeTextBack';
        box.style.position = 'absolute';
        box.style.left = '5px';
        box.style.top = '5px';
        box.style.width = '119px';
        box.style.height = '58px';
        box.style.background = 'rgba(0,0,0,0.2)';
        box.style.zIndex = 9;
        box.style.opacity = 0;
    
        var text = document.createElement('div');
        text.id = 'modeText';
        text.style.position = 'absolute';
        text.style.left = '0px';
        text.style.top = '41px';
        text.style.width = '119px';
        text.style.fontSize = '12px';
        text.style.fontFamily = 'monospace';
        text.style.color = 'white';
        text.style.textAlign = 'center';
        text.style.textShadow = '1px 1px 0 rgba(0,0,0,0.5)';
        text.innerHTML = this.isWebGL() ? 'WebGL mode' : 'Canvas mode';
    
        document.body.appendChild(box);
        box.appendChild(text);
    
        this._modeBox = box;
    };

    /**
     * @static
     * @method _createGameFontLoader
     * @private
     */
    static _createGameFontLoader(): void {
        this._createFontLoader('GameFont');
    };

    /**
     * @static
     * @method _createFontLoader
     * @param {String} name
     * @private
     */
    static _createFontLoader(name: string): void {
        var div = document.createElement('div');
        var text = document.createTextNode('.');
        div.style.fontFamily = name;
        div.style.fontSize = '0px';
        div.style.color = 'transparent';
        div.style.position = 'absolute';
        div.style.margin = 'auto';
        div.style.top = '0px';
        div.style.left = '0px';
        div.style.width = '1px';
        div.style.height = '1px';
        div.appendChild(text);
        document.body.appendChild(div);
    };

    /**
     * @static
     * @method _centerElement
     * @param {HTMLElement} element
     * @private
     */
    static _centerElement(element: HTMLElement): void {
        var width = element.width * this._realScale;
        var height = element.height * this._realScale;
        element.style.position = 'absolute';
        element.style.margin = 'auto';
        element.style.top = 0;
        element.style.left = 0;
        element.style.right = 0;
        element.style.bottom = 0;
        element.style.width = width + 'px';
        element.style.height = height + 'px';
    };

    /**
     * @static
     * @method _disableTextSelection
     * @private
     */
    static _disableTextSelection(): void {
        var body = document.body;
        body.style.userSelect = 'none';
        body.style.webkitUserSelect = 'none';
        body.style.msUserSelect = 'none';
        body.style.mozUserSelect = 'none';
    };

    /**
     * @static
     * @method _disableContextMenu
     * @private
     */
    static _disableContextMenu(): void {
        var elements = document.body.getElementsByTagName('*');
        var oncontextmenu = function() { return false; };
        for (var i = 0; i < elements.length; i++) {
            elements[i].oncontextmenu = oncontextmenu;
        }
    };

    /**
     * @static
     * @method _applyCanvasFilter
     * @private
     */
    static _applyCanvasFilter(): void {
        if (this._canvas) {
            this._canvas.style.opacity = 0.5;
            this._canvas.style.filter = 'blur(8px)';
            this._canvas.style.webkitFilter = 'blur(8px)';
        }
    };

    /**
     * @static
     * @method _onVideoLoad
     * @private
     */
    static _onVideoLoad(): void {
        this._video.play();
        this._updateVisibility(true);
        this._videoLoading = false;
    };

    /**
     * @static
     * @method _onVideoError
     * @private
     */
    static _onVideoError(): void {
        this._updateVisibility(false);
        this._videoLoading = false;
    };

    /**
     * @static
     * @method _onVideoEnd
     * @private
     */
    static _onVideoEnd(): void {
        this._updateVisibility(false);
    };

    /**
     * @static
     * @method _updateVisibility
     * @param {Boolean} videoVisible
     * @private
     */
    static _updateVisibility(videoVisible: boolean): void  {
        this._video.style.opacity = videoVisible ? 1 : 0;
        this._canvas.style.opacity = videoVisible ? 0 : 1;
    };

    /**
     * @static
     * @method _isVideoVisible
     * @return {Boolean}
     * @private
     */
    static _isVideoVisible(): boolean {
        return this._video.style.opacity > 0;
    };

    /**
     * @static
     * @method _setupEventHandlers
     * @private
     */
    static _setupEventHandlers(): void {
        window.addEventListener('resize', this._onWindowResize.bind(this));
        document.addEventListener('keydown', this._onKeyDown.bind(this));
        document.addEventListener('keydown', this._onTouchEnd.bind(this));
        document.addEventListener('mousedown', this._onTouchEnd.bind(this));
        document.addEventListener('touchend', this._onTouchEnd.bind(this));
    };

    /**
     * @static
     * @method _onWindowResize
     * @private
     */
    static _onWindowResize(): void {
        this._updateAllElements();
    };

    /**
     * @static
     * @method _onKeyDown
     * @param {KeyboardEvent} event
     * @private
     */
    static _onKeyDown(event: KeyboardEvent): void {
        if (!event.ctrlKey && !event.altKey) {
            switch (event.keyCode) {
            case 113:   // F2
                event.preventDefault();
                this._switchFPSMeter();
                break;
            case 114:   // F3
                event.preventDefault();
                this._switchStretchMode();
                break;
            case 115:   // F4
                event.preventDefault();
                this._switchFullScreen();
                break;
            }
        }
    };

    /**
     * @static
     * @method _onTouchEnd
     * @param {TouchEvent} event
     * @private
     */
    static _onTouchEnd(event: TouchEvent): void {
        if (!this._videoUnlocked) {
            this._video.play();
            this._videoUnlocked = true;
        }
        if (this._isVideoVisible() && this._video.paused) {
            this._video.play();
        }
    };

    /**
     * @static
     * @method _switchFPSMeter
     * @private
     */
    static _switchFPSMeter(): void {
        if (this._fpsMeter.isPaused) {
            this.showFps();
            this._fpsMeter.showFps();
            this._fpsMeterToggled = false;
        } else if (!this._fpsMeterToggled) {
            this._fpsMeter.showDuration();
            this._fpsMeterToggled = true;
        } else {
            this.hideFps();
        }
    };

    /**
     * @static
     * @method _switchStretchMode
     * @return {Boolean}
     * @private
     */
    static _switchStretchMode(): void {
        this._stretchEnabled = !this._stretchEnabled;
        this._updateAllElements();
    };

    /**
     * @static
     * @method _switchFullScreen
     * @private
     */   
    static _switchFullScreen(): void {
        if (this._isFullScreen()) {
            this._requestFullScreen();
        } else {
            this._cancelFullScreen();
        }
    };

    /**
     * @static
     * @method _isFullScreen
     * @return {Boolean}
     * @private
     */
    static _isFullScreen(): boolean {
        return ((document.fullScreenElement && document.fullScreenElement !== null) ||
                (!document.mozFullScreen && !document.webkitFullscreenElement &&
                 !document.msFullscreenElement));
        
    };

    /**
     * @static
     * @method _requestFullScreen
     * @private
     */
    static _requestFullScreen(): void {
        var element = document.body;
        if (element.requestFullScreen) {
            element.requestFullScreen();
        } else if (element.mozRequestFullScreen) {
            element.mozRequestFullScreen();
        } else if (element.webkitRequestFullScreen) {
            element.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
        } else if (element.msRequestFullscreen) {
            element.msRequestFullscreen();
        }
    };

    /**
     * @static
     * @method _cancelFullScreen
     * @private
     */
    static _cancelFullScreen(): void {
        if (document.cancelFullScreen) {
            document.cancelFullScreen();
        } else if (document.mozCancelFullScreen) {
            document.mozCancelFullScreen();
        } else if (document.webkitCancelFullScreen) {
            document.webkitCancelFullScreen();
        } else if (document.msExitFullscreen) {
            document.msExitFullscreen();
        }
    };

}

//-----------------------------------------------------------------------------
/**
 * The static class that handles input data from the keyboard and gamepads.
 *
 * @class Input
 */
class Input {
    private constructor(){
        throw new Error('This is a static class');
    };

    /**
     * Initializes the input system.
     *
     * @static
     * @method initialize
     */
    static initialize(): void {
        this.clear();
        this._wrapNwjsAlert();
        this._setupEventHandlers();
    };

    /**
     * The wait time of the key repeat in frames.
     *
     * @static
     * @property keyRepeatWait
     * @type Number
     */
    static keyRepeatWait: number = 24;

    /**
     * The interval of the key repeat in frames.
     *
     * @static
     * @property keyRepeatInterval
     * @type Number
     */
    static keyRepeatInterval: number = 6;
    /**
     * A hash table to convert from a virtual key code to a mapped key name.
     *
     * @static
     * @property keyMapper
     * @type Object
     */
    static keyMapper: { [key: number]: string } = {
        9: 'tab',       // tab
        13: 'ok',       // enter
        16: 'shift',    // shift
        17: 'control',  // control
        18: 'control',  // alt
        27: 'escape',   // escape
        32: 'ok',       // space
        33: 'pageup',   // pageup
        34: 'pagedown', // pagedown
        37: 'left',     // left arrow
        38: 'up',       // up arrow
        39: 'right',    // right arrow
        40: 'down',     // down arrow
        45: 'escape',   // insert
        81: 'pageup',   // Q
        87: 'pagedown', // W
        88: 'escape',   // X
        90: 'ok',       // Z
        96: 'escape',   // numpad 0
        98: 'down',     // numpad 2
        100: 'left',    // numpad 4
        102: 'right',   // numpad 6
        104: 'up',      // numpad 8
        120: 'debug'    // F9
    };

    /**
     * A hash table to convert from a gamepad button to a mapped key name.
     *
     * @static
     * @property gamepadMapper
     * @type Object
     */
    static gamepadMapper: { [key: number]: string } = {
        0: 'ok',        // A
        1: 'cancel',    // B
        2: 'shift',     // X
        3: 'menu',      // Y
        4: 'pageup',    // LB
        5: 'pagedown',  // RB
        12: 'up',       // D-pad up
        13: 'down',     // D-pad down
        14: 'left',     // D-pad left
        15: 'right',    // D-pad right
    };

    /**
     * Clears all the input data.
     *
     * @static
     * @method clear
     */
    static clear(): void {
        this._currentState = {};
        this._previousState = {};
        this._gamepadStates = [];
        this._latestButton = null;
        this._pressedTime = 0;
        this._dir4 = 0;
        this._dir8 = 0;
        this._preferredAxis = '';
        this._date = 0;
    };

    /**
     * Updates the input data.
     *
     * @static
     * @method update
     */
    static update(): void {
        this._pollGamepads();
        if (this._currentState[this._latestButton]) {
            this._pressedTime++;
        } else {
            this._latestButton = null;
        }
        for (var name in this._currentState) {
            if (this._currentState[name] && !this._previousState[name]) {
                this._latestButton = name;
                this._pressedTime = 0;
                this._date = Date.now();
            }
            this._previousState[name] = this._currentState[name];
        }
        this._updateDirection();
    };

    /**
     * Checks whether a key is currently pressed down.
     *
     * @static
     * @method isPressed
     * @param {String} keyName The mapped name of the key
     * @return {Boolean} True if the key is pressed
     */
    static isPressed(keyName: string): boolean{
        if (this._isEscapeCompatible(keyName) && this.isPressed('escape')) {
            return true;
        } else {
            return !!this._currentState[keyName];
        }
    };

    /**
     * Checks whether a key is just pressed.
     *
     * @static
     * @method isTriggered
     * @param {String} keyName The mapped name of the key
     * @return {Boolean} True if the key is triggered
     */
    static isTriggered(keyName: string): boolean {
        if (this._isEscapeCompatible(keyName) && this.isTriggered('escape')) {
            return true;
        } else {
            return this._latestButton === keyName && this._pressedTime === 0;
        }
    };
 
    /**
     * Checks whether a key is just pressed or a key repeat occurred.
     *
     * @static
     * @method isRepeated
     * @param {String} keyName The mapped name of the key
     * @return {Boolean} True if the key is repeated
     */
    static isRepeated(keyName: string): boolean {
        if (this._isEscapeCompatible(keyName) && this.isRepeated('escape')) {
            return true;
        } else {
            return (this._latestButton === keyName &&
                    (this._pressedTime === 0 ||
                     (this._pressedTime >= this.keyRepeatWait &&
                      this._pressedTime % this.keyRepeatInterval === 0)));
        }
    };

    /**
     * Checks whether a key is kept depressed.
     *
     * @static
     * @method isLongPressed
     * @param {String} keyName The mapped name of the key
     * @return {Boolean} True if the key is long-pressed
     */
    static isLongPressed(keyName: string): boolean {
        if (this._isEscapeCompatible(keyName) && this.isLongPressed('escape')) {
            return true;
        } else {
            return (this._latestButton === keyName &&
                    this._pressedTime >= this.keyRepeatWait);
        }
    };

    /**
     * [read-only] The four direction value as a number of the numpad, or 0 for neutral.
     *
     * @static
     * @property dir4
     * @type Number
     */
   
    public static get dir4():number{
        return this._dir4;
    }
 
    /**
     * [read-only] The eight direction value as a number of the numpad, or 0 for neutral.
     *
     * @static
     * @property dir8
     * @type Number
     */
    public static get dir8():number{
        return this._dir8;
    }

    /**
     * [read-only] The time of the last input in milliseconds.
     *
     * @static
     * @property date
     * @type Number
     */
    public static get date():number{
        return this._date;
    }

    /**
     * @static
     * @method _wrapNwjsAlert
     * @private
     */
    static _wrapNwjsAlert(): void {
        if (Utils.isNwjs()) {
            var _alert = window.alert;
            window.alert = function() {
                var gui = require('nw.gui');
                var win = gui.Window.get();
                _alert.apply(this, arguments);
                win.focus();
                Input.clear();
            };
        }
    };

    /**
     * @static
     * @method _setupEventHandlers
     * @private
     */
    static _setupEventHandlers(): void {
        document.addEventListener('keydown', this._onKeyDown.bind(this));
        document.addEventListener('keyup', this._onKeyUp.bind(this));
        window.addEventListener('blur', this._onLostFocus.bind(this));
    };

    /**
     * @static
     * @method _onKeyDown
     * @param {KeyboardEvent} event
     * @private
     */
    static _onKeyDown(event: KeyboardEvent): void {
        if (this._shouldPreventDefault(event.keyCode)) {
            event.preventDefault();
        }
        if (event.keyCode === 144) {    // Numlock
            this.clear();
        }
        var buttonName = this.keyMapper[event.keyCode];
        if (ResourceHandler.exists() && buttonName === 'ok') {
            ResourceHandler.retry();
        } else if (buttonName) {
            this._currentState[buttonName] = true;
        }
    };

    /**
     * @static
     * @method _shouldPreventDefault
     * @param {Number} keyCode
     * @private
     */
    static _shouldPreventDefault(keyCode: number): boolean {
        switch (keyCode) {
        case 8:     // backspace
        case 33:    // pageup
        case 34:    // pagedown
        case 37:    // left arrow
        case 38:    // up arrow
        case 39:    // right arrow
        case 40:    // down arrow
            return true;
        }
        return false;
    };

    /**
     * @static
     * @method _onKeyUp
     * @param {KeyboardEvent} event
     * @private
     */
    static _onKeyUp(event: KeyboardEvent): void {
        var buttonName = this.keyMapper[event.keyCode];
        if (buttonName) {
            this._currentState[buttonName] = false;
        }
        if (event.keyCode === 0) {  // For QtWebEngine on OS X
            this.clear();
        }
    };

    /**
     * @static
     * @method _onLostFocus
     * @private
     */
    static _onLostFocus(): void {
        this.clear();
    };

    /**
     * @static
     * @method _pollGamepads
     * @private
     */
    static _pollGamepads(): void {
        if (navigator.getGamepads) {
            var gamepads = navigator.getGamepads();
            if (gamepads) {
                for (var i = 0; i < gamepads.length; i++) {
                    var gamepad = gamepads[i];
                    if (gamepad && gamepad.connected) {
                        this._updateGamepadState(gamepad);
                    }
                }
            }
        }
    };

    /**
     * @static
     * @method _updateGamepadState
     * @param {Gamepad} gamepad
     * @param {Number} index
     * @private
     */
    static _updateGamepadState(gamepad: Gamepad): void {
        var lastState = this._gamepadStates[gamepad.index] || [];
        var newState = [];
        var buttons = gamepad.buttons;
        var axes = gamepad.axes;
        var threshold = 0.5;
        newState[12] = false;
        newState[13] = false;
        newState[14] = false;
        newState[15] = false;
        for (var i = 0; i < buttons.length; i++) {
            newState[i] = buttons[i].pressed;
        }
        if (axes[1] < -threshold) {
            newState[12] = true;    // up
        } else if (axes[1] > threshold) {
            newState[13] = true;    // down
        }
        if (axes[0] < -threshold) {
            newState[14] = true;    // left
        } else if (axes[0] > threshold) {
            newState[15] = true;    // right
        }
        for (var j = 0; j < newState.length; j++) {
            if (newState[j] !== lastState[j]) {
                var buttonName = this.gamepadMapper[j];
                if (buttonName) {
                    this._currentState[buttonName] = newState[j];
                }
            }
        }
        this._gamepadStates[gamepad.index] = newState;
    };

    /**
     * @static
     * @method _updateDirection
     * @private
     */
    static _updateDirection(): void {
        var x = this._signX();
        var y = this._signY();
    
        this._dir8 = this._makeNumpadDirection(x, y);
    
        if (x !== 0 && y !== 0) {
            if (this._preferredAxis === 'x') {
                y = 0;
            } else {
                x = 0;
            }
        } else if (x !== 0) {
            this._preferredAxis = 'y';
        } else if (y !== 0) {
            this._preferredAxis = 'x';
        }
    
        this._dir4 = this._makeNumpadDirection(x, y);
    };

    /**
     * @static
     * @method _signX
     * @private
     */
    static _signX(): number {
        var x = 0;
    
        if (this.isPressed('left')) {
            x--;
        }
        if (this.isPressed('right')) {
            x++;
        }
        return x;
    };

    /**
     * @static
     * @method _signY
     * @private
     */
    static _signY(): number {
        var y = 0;
    
        if (this.isPressed('up')) {
            y--;
        }
        if (this.isPressed('down')) {
            y++;
        }
        return y;
    };
 
    /**
     * @static
     * @method _makeNumpadDirection
     * @param {Number} x
     * @param {Number} y
     * @return {Number}
     * @private
     */
    static _makeNumpadDirection(x: number, y: number): number {
        if (x !== 0 || y !== 0) {
            return  5 - y * 3 + x;
        }
        return 0;
    };

    /**
     * @static
     * @method _isEscapeCompatible
     * @param {String} keyName
     * @return {Boolean}
     * @private
     */
    static _isEscapeCompatible(keyName: string): boolean {
        return keyName === 'cancel' || keyName === 'menu';
    };
 
    static _currentState: { [key: string]: boolean };
    static _previousState: { [key: string]: boolean };
    static _gamepadStates: boolean[][];
    static _latestButton: string;
    static _pressedTime: number;
    static _dir4: number;
    static _dir8: number;
    static _preferredAxis: string;
    static _date: number

}

declare interface IDataTouchInput {
    triggered: boolean;
    cancelled: boolean;
    moved: boolean;
    released: boolean;
    wheelX: number;
    wheelY: number;
}

//-----------------------------------------------------------------------------
/**
 * The static class that handles input data from the mouse and touchscreen.
 *
 * @class TouchInput
 */
class TouchInput {
    private constructor(){
        throw new Error('This is a static class');
    };

    /**
     * Initializes the touch system.
     *
     * @static
     * @method initialize
     */
    static initialize(): void {
        this.clear();
        this._setupEventHandlers();
    };

    /**
     * The wait time of the pseudo key repeat in frames.
     *
     * @static
     * @property keyRepeatWait
     * @type Number
     */
    static keyRepeatWait: number = 24;

    /**
     * The interval of the pseudo key repeat in frames.
     *
     * @static
     * @property keyRepeatInterval
     * @type Number
     */
    static keyRepeatInterval: number = 6;

    /**
     * Clears all the touch data.
     *
     * @static
     * @method clear
     */
    static clear(): void {
        this._mousePressed = false;
        this._screenPressed = false;
        this._pressedTime = 0;
        this._events = {};
        this._events.triggered = false;
        this._events.cancelled = false;
        this._events.moved = false;
        this._events.released = false;
        this._events.wheelX = 0;
        this._events.wheelY = 0;
        this._triggered = false;
        this._cancelled = false;
        this._moved = false;
        this._released = false;
        this._wheelX = 0;
        this._wheelY = 0;
        this._x = 0;
        this._y = 0;
        this._date = 0;
    };
    
    static _mousePressed: boolean;
    static _screenPressed: boolean;
    static _pressedTime: number;
    static _events: IDataTouchInput | any;
    static _triggered: boolean;
    static _cancelled: boolean;
    static _moved: boolean;
    static _released: boolean;
    static _wheelX: number;
    static _wheelY: number;
    static _x: number;
    static _y: number;
    static _date: number;

    /**
     * Updates the touch data.
     *
     * @static
     * @method update
     */
    static update(): void {
        this._triggered = this._events.triggered;
        this._cancelled = this._events.cancelled;
        this._moved = this._events.moved;
        this._released = this._events.released;
        this._wheelX = this._events.wheelX;
        this._wheelY = this._events.wheelY;
        this._events.triggered = false;
        this._events.cancelled = false;
        this._events.moved = false;
        this._events.released = false;
        this._events.wheelX = 0;
        this._events.wheelY = 0;
        if (this.isPressed()) {
            this._pressedTime++;
        }
    };

    /**
     * Checks whether the mouse button or touchscreen is currently pressed down.
     *
     * @static
     * @method isPressed
     * @return {Boolean} True if the mouse button or touchscreen is pressed
     */
    static isPressed(): boolean {
        return this._mousePressed || this._screenPressed;
    };

    /**
     * Checks whether the left mouse button or touchscreen is just pressed.
     *
     * @static
     * @method isTriggered
     * @return {Boolean} True if the mouse button or touchscreen is triggered
     */   
    static isTriggered(): boolean {
        return this._triggered;
    };

    /**
     * Checks whether the left mouse button or touchscreen is just pressed
     * or a pseudo key repeat occurred.
     *
     * @static
     * @method isRepeated
     * @return {Boolean} True if the mouse button or touchscreen is repeated
     */   
    static isRepeated(): boolean {
        return (this.isPressed() &&
                (this._triggered ||
                 (this._pressedTime >= this.keyRepeatWait &&
                  this._pressedTime % this.keyRepeatInterval === 0)));
    };

    /**
     * Checks whether the left mouse button or touchscreen is kept depressed.
     *
     * @static
     * @method isLongPressed
     * @return {Boolean} True if the left mouse button or touchscreen is long-pressed
     */
    static isLongPressed(): boolean {
        return this.isPressed() && this._pressedTime >= this.keyRepeatWait;
    };

    /**
     * Checks whether the right mouse button is just pressed.
     *
     * @static
     * @method isCancelled
     * @return {Boolean} True if the right mouse button is just pressed
     */
    static isCancelled(): boolean {
        return this._cancelled;
    };

    /**
     * Checks whether the mouse or a finger on the touchscreen is moved.
     *
     * @static
     * @method isMoved
     * @return {Boolean} True if the mouse or a finger on the touchscreen is moved
     */    
    static isMoved(): boolean {
        return this._moved;
    };

    /**
     * Checks whether the left mouse button or touchscreen is released.
     *
     * @static
     * @method isReleased
     * @return {Boolean} True if the mouse button or touchscreen is released
     */
    static isReleased(): boolean {
        return this._released;
    };

    /**
     * [read-only] The horizontal scroll amount.
     *
     * @static
     * @property wheelX
     * @type Number
     */
    public static get wheelX():number{
        return this._wheelX;
    }

    /**
     * [read-only] The vertical scroll amount.
     *
     * @static
     * @property wheelY
     * @type Number
     */
    public static get wheelY():number{
        return this._wheelY;
    }

    /**
     * [read-only] The x coordinate on the canvas area of the latest touch event.
     *
     * @static
     * @property x
     * @type Number
     */
    public static get x():number{
        return this._x;
    }

    /**
     * [read-only] The y coordinate on the canvas area of the latest touch event.
     *
     * @static
     * @property y
     * @type Number
     */
    public static get y():number{
        return this._y;
    }

    /**
     * [read-only] The time of the last input in milliseconds.
     *
     * @static
     * @property date
     * @type Number
     */
    public static get date():number{
        return this._date;
    }

    /**
     * @static
     * @method _setupEventHandlers
     * @private
     */
    static _setupEventHandlers(): void {
        var isSupportPassive = Utils.isSupportPassiveEvent();
        document.addEventListener('mousedown', this._onMouseDown.bind(this));
        document.addEventListener('mousemove', this._onMouseMove.bind(this));
        document.addEventListener('mouseup', this._onMouseUp.bind(this));
        document.addEventListener('wheel', this._onWheel.bind(this));
        document.addEventListener('touchstart', this._onTouchStart.bind(this), isSupportPassive ? {passive: false} : false);
        document.addEventListener('touchmove', this._onTouchMove.bind(this), isSupportPassive ? {passive: false} : false);
        document.addEventListener('touchend', this._onTouchEnd.bind(this));
        document.addEventListener('touchcancel', this._onTouchCancel.bind(this));
        document.addEventListener('pointerdown', this._onPointerDown.bind(this));
    };

    /**
     * @static
     * @method _onMouseDown
     * @param {MouseEvent} event
     * @private
     */
    static _onMouseDown(event: MouseEvent): void {
        if (event.button === 0) {
            this._onLeftButtonDown(event);
        } else if (event.button === 1) {
            this._onMiddleButtonDown(event);
        } else if (event.button === 2) {
            this._onRightButtonDown(event);
        }
    };

    /**
     * @static
     * @method _onLeftButtonDown
     * @param {MouseEvent} event
     * @private
     */    
    static _onLeftButtonDown(event: MouseEvent): void {
        var x = Graphics.pageToCanvasX(event.pageX);
        var y = Graphics.pageToCanvasY(event.pageY);
        if (Graphics.isInsideCanvas(x, y)) {
            this._mousePressed = true;
            this._pressedTime = 0;
            this._onTrigger(x, y);
        }
    };

    /**
     * @static
     * @method _onMiddleButtonDown
     * @param {MouseEvent} event
     * @private
     */    
    static _onMiddleButtonDown(event: MouseEvent): void {
    };

    /**
     * @static
     * @method _onRightButtonDown
     * @param {MouseEvent} event
     * @private
     */   
    static _onRightButtonDown(event: MouseEvent): void {
        var x = Graphics.pageToCanvasX(event.pageX);
        var y = Graphics.pageToCanvasY(event.pageY);
        if (Graphics.isInsideCanvas(x, y)) {
            this._onCancel(x, y);
        }
    };

    /**
     * @static
     * @method _onMouseMove
     * @param {MouseEvent} event
     * @private
     */    
    static _onMouseMove(event: MouseEvent): void {
        if (this._mousePressed) {
            var x = Graphics.pageToCanvasX(event.pageX);
            var y = Graphics.pageToCanvasY(event.pageY);
            this._onMove(x, y);
        }
    };

    /**
     * @static
     * @method _onMouseUp
     * @param {MouseEvent} event
     * @private
     */   
    static _onMouseUp(event: MouseEvent): void {
        if (event.button === 0) {
            var x = Graphics.pageToCanvasX(event.pageX);
            var y = Graphics.pageToCanvasY(event.pageY);
            this._mousePressed = false;
            this._onRelease(x, y);
        }
    };

    /**
     * @static
     * @method _onWheel
     * @param {WheelEvent} event
     * @private
     */    
    static _onWheel(event: WheelEvent): void {
        this._events.wheelX += event.deltaX;
        this._events.wheelY += event.deltaY;
        event.preventDefault();
    };

    /**
     * @static
     * @method _onTouchStart
     * @param {TouchEvent} event
     * @private
     */    
    static _onTouchStart(event: TouchEvent): void {
        for (var i = 0; i < event.changedTouches.length; i++) {
            var touch = event.changedTouches[i];
            var x = Graphics.pageToCanvasX(touch.pageX);
            var y = Graphics.pageToCanvasY(touch.pageY);
            if (Graphics.isInsideCanvas(x, y)) {
                this._screenPressed = true;
                this._pressedTime = 0;
                if (event.touches.length >= 2) {
                    this._onCancel(x, y);
                } else {
                    this._onTrigger(x, y);
                }
                event.preventDefault();
            }
        }
        if (window.cordova || window.navigator.standalone) {
            event.preventDefault();
        }
    };

 
    /**
     * @static
     * @method _onTouchMove
     * @param {TouchEvent} event
     * @private
     */
    static _onTouchMove(event: TouchEvent): void {
        for (var i = 0; i < event.changedTouches.length; i++) {
            var touch = event.changedTouches[i];
            var x = Graphics.pageToCanvasX(touch.pageX);
            var y = Graphics.pageToCanvasY(touch.pageY);
            this._onMove(x, y);
        }
    };
 
    /**
     * @static
     * @method _onTouchEnd
     * @param {TouchEvent} event
     * @private
     */
    static _onTouchEnd(event: TouchEvent): void {
        for (var i = 0; i < event.changedTouches.length; i++) {
            var touch = event.changedTouches[i];
            var x = Graphics.pageToCanvasX(touch.pageX);
            var y = Graphics.pageToCanvasY(touch.pageY);
            this._screenPressed = false;
            this._onRelease(x, y);
        }
    };

    /**
     * @static
     * @method _onTouchCancel
     * @param {TouchEvent} event
     * @private
     */
    static _onTouchCancel(event: TouchEvent): void {
        this._screenPressed = false;
    };

    /**
     * @static
     * @method _onPointerDown
     * @param {PointerEvent} event
     * @private
     */    
    static _onPointerDown(event: PointerEvent): void {
        if (event.pointerType === 'touch' && !event.isPrimary) {
            var x = Graphics.pageToCanvasX(event.pageX);
            var y = Graphics.pageToCanvasY(event.pageY);
            if (Graphics.isInsideCanvas(x, y)) {
                // For Microsoft Edge
                this._onCancel(x, y);
                event.preventDefault();
            }
        }
    };

    /**
     * @static
     * @method _onTrigger
     * @param {Number} x
     * @param {Number} y
     * @private
     */
    static _onTrigger(x: number, y: number): void {
        this._events.triggered = true;
        this._x = x;
        this._y = y;
        this._date = Date.now();
    };

    /**
     * @static
     * @method _onCancel
     * @param {Number} x
     * @param {Number} y
     * @private
     */   
    static _onCancel(x: number, y: number): void {
        this._events.cancelled = true;
        this._x = x;
        this._y = y;
    };

    /**
     * @static
     * @method _onMove
     * @param {Number} x
     * @param {Number} y
     * @private
     */   
    static _onMove(x: number, y: number): void {
        this._events.moved = true;
        this._x = x;
        this._y = y;
    };

    /**
     * @static
     * @method _onRelease
     * @param {Number} x
     * @param {Number} y
     * @private
     */   
    static _onRelease(x: number, y: number): void {
        this._events.released = true;
        this._x = x;
        this._y = y;
    };
}

//-----------------------------------------------------------------------------
/**
 * The basic object that is rendered to the game screen.
 *
 * @class Sprite
 * @constructor
 * @param {Bitmap} bitmap The image for the sprite
 */
class Sprite extends PIXI.Sprite {
    //FIXME 扩展
    z: number;
    ax: number;
    ay: number;
    
    constructor(bitmap?: Bitmap){
        super();
        this.initialize.apply(this, arguments);
    }
 
    static voidFilter: PIXI.filters.VoidFilter = new PIXI.filters.VoidFilter();

    initialize(bitmap?: Bitmap): void {
        var texture = new PIXI.Texture(new PIXI.BaseTexture());
    
        PIXI.Sprite.call(this, texture);
    
        this._bitmap = null;
        this._frame = new Rectangle();
        this._realFrame = new Rectangle();
        this._blendColor = [0, 0, 0, 0];
        this._colorTone = [0, 0, 0, 0];
        this._canvas = null;
        this._context = null;
        this._tintTexture = null;
    
        /**
         * use heavy renderer that will reduce border artifacts and apply advanced blendModes
         * @type {boolean}
         * @private
         */
        this._isPicture = false;
    
        this.spriteId = Sprite._counter++;
        this.opaque = false;
    
        this.bitmap = bitmap;
    };

    // Number of the created objects.
    static _counter: number = 0;

    /**
     * The image for the sprite.
     *
     * @property bitmap
     * @type Bitmap
     */
    public get bitmap():Bitmap{
        return this._bitmap;
    }
 
    public set bitmap(value:Bitmap){
        if (this._bitmap !== value) {
            this._bitmap = value;

            if(value){
                this._refreshFrame = true;
                value.addLoadListener(this._onBitmapLoad.bind(this));
            }else{
                this._refreshFrame = false;
                this.texture.frame = Rectangle.emptyRectangle;
            }
        }
    }

    /**
     * The width of the sprite without the scale.
     *
     * @property width
     * @type Number
     */
    public get width():number{
        return this._frame.width;
    }
 
    public set width(value:number){
        this._frame.width = value;
        this._refresh();
    }

    /**
     * The height of the sprite without the scale.
     *
     * @property height
     * @type Number
     */
    public get height():number{
        return this._frame.height;
    }
 
    public set height(value:number){
        this._frame.height = value;
        this._refresh();
    }

    /**
     * The opacity of the sprite (0 to 255).
     *
     * @property opacity
     * @type Number
     */
    public get opacity():number{
        return this.alpha * 255;
    }
 
    public set opacity(value:number){
        this.alpha = value.clamp(0, 255) / 255;
       
    }

    /**
     * Updates the sprite for each frame.
     *
     * @method update
     */
    update(): void {
        this.children.forEach(function(child) {
            if (child.update) {
                child.update();
            }
        });
    };

    /**
     * Sets the x and y at once.
     *
     * @method move
     * @param {Number} x The x coordinate of the sprite
     * @param {Number} y The y coordinate of the sprite
     */    
    move(x: number, y: number,width?: any, height?: any): void {
        this.x = x;
        this.y = y;
    };

    /**
     * Sets the rectagle of the bitmap that the sprite displays.
     *
     * @method setFrame
     * @param {Number} x The x coordinate of the frame
     * @param {Number} y The y coordinate of the frame
     * @param {Number} width The width of the frame
     * @param {Number} height The height of the frame
     */
    setFrame(x: number, y: number, width: number, height: number): void {
        this._refreshFrame = false;
        var frame = this._frame;
        if (x !== frame.x || y !== frame.y ||
                width !== frame.width || height !== frame.height) {
            frame.x = x;
            frame.y = y;
            frame.width = width;
            frame.height = height;
            this._refresh();
        }
    };
  
    /**
     * Gets the blend color for the sprite.
     *
     * @method getBlendColor
     * @return {Array} The blend color [r, g, b, a]
     */
    getBlendColor(): number {
        return this._blendColor.clone();
    };

    /**
     * Sets the blend color for the sprite.
     *
     * @method setBlendColor
     * @param {Array} color The blend color [r, g, b, a]
     */
    setBlendColor(color: any): void {
        if (!(color instanceof Array)) {
            throw new Error('Argument must be an array');
        }
        if (!this._blendColor.equals(color)) {
            this._blendColor = color.clone();
            this._refresh();
        }
    };

    /**
     * Gets the color tone for the sprite.
     *
     * @method getColorTone
     * @return {Array} The color tone [r, g, b, gray]
     */
    getColorTone(): number {
        return this._colorTone.clone();
    };

    /**
     * Sets the color tone for the sprite.
     *
     * @method setColorTone
     * @param {Array} tone The color tone [r, g, b, gray]
     */
    setColorTone(tone: any): void {
        if (!(tone instanceof Array)) {
            throw new Error('Argument must be an array');
        }
        if (!this._colorTone.equals(tone)) {
            this._colorTone = tone.clone();
            this._refresh();
        }
    };

    /**
     * @method _onBitmapLoad
     * @private
     */
    _onBitmapLoad(bitmapLoaded: Bitmap): void {
        if(bitmapLoaded === this._bitmap){
            if (this._refreshFrame && this._bitmap) {
                this._refreshFrame = false;
                this._frame.width = this._bitmap.width;
                this._frame.height = this._bitmap.height;
            }
        }
    
        this._refresh();
    };

    /**
     * @method _refresh
     * @private
     */ 
    _refresh(): void {
        var frameX = Math.floor(this._frame.x);
        var frameY = Math.floor(this._frame.y);
        var frameW = Math.floor(this._frame.width);
        var frameH = Math.floor(this._frame.height);
        var bitmapW = this._bitmap ? this._bitmap.width : 0;
        var bitmapH = this._bitmap ? this._bitmap.height : 0;
        var realX = frameX.clamp(0, bitmapW);
        var realY = frameY.clamp(0, bitmapH);
        var realW = (frameW - realX + frameX).clamp(0, bitmapW - realX);
        var realH = (frameH - realY + frameY).clamp(0, bitmapH - realY);
    
        this._realFrame.x = realX;
        this._realFrame.y = realY;
        this._realFrame.width = realW;
        this._realFrame.height = realH;
        this.pivot.x = frameX - realX;
        this.pivot.y = frameY - realY;
    
        if (realW > 0 && realH > 0) {
            if (this._needsTint()) {
                this._createTinter(realW, realH);
                this._executeTint(realX, realY, realW, realH);
                this._tintTexture.update();
                this.texture.baseTexture = this._tintTexture;
                this.texture.frame = new Rectangle(0, 0, realW, realH);
            } else {
                if (this._bitmap) {
                    this.texture.baseTexture = this._bitmap.baseTexture;
                }
                this.texture.frame = this._realFrame;
            }
        } else if (this._bitmap) {
            this.texture.frame = Rectangle.emptyRectangle;
        } else {
            this.texture.baseTexture.width = Math.max(this.texture.baseTexture.width, this._frame.x + this._frame.width);
            this.texture.baseTexture.height = Math.max(this.texture.baseTexture.height, this._frame.y + this._frame.height);
            this.texture.frame = this._frame;
        }
        this.texture._updateID++;
    };

    /**
     * @method _isInBitmapRect
     * @param {Number} x
     * @param {Number} y
     * @param {Number} w
     * @param {Number} h
     * @return {Boolean}
     * @private
     */
    _isInBitmapRect(x: number, y: number, w: number, h: number): boolean {
        return (this._bitmap && x + w > 0 && y + h > 0 &&
                x < this._bitmap.width && y < this._bitmap.height);
    };

    /**
     * @method _needsTint
     * @return {Boolean}
     * @private
     */   
    _needsTint(): any {
        var tone = this._colorTone;
        return tone[0] || tone[1] || tone[2] || tone[3] || this._blendColor[3] > 0;
    };
    
    /**
     * @method _createTinter
     * @param {Number} w
     * @param {Number} h
     * @private
     */
    _createTinter(w: number, h: number): void {
        if (!this._canvas) {
            this._canvas = document.createElement('canvas');
            this._context = this._canvas.getContext('2d');
        }
    
        this._canvas.width = w;
        this._canvas.height = h;
    
        if (!this._tintTexture) {
            this._tintTexture = new PIXI.BaseTexture(this._canvas);
        }
    
        this._tintTexture.width = w;
        this._tintTexture.height = h;
        this._tintTexture.scaleMode = this._bitmap.baseTexture.scaleMode;
    };

    /**
     * @method _executeTint
     * @param {Number} x
     * @param {Number} y
     * @param {Number} w
     * @param {Number} h
     * @private
     */   
    _executeTint(x: number, y: number, w: number, h: number): void {
        var context = this._context;
        var tone = this._colorTone;
        var color = this._blendColor;
    
        context.globalCompositeOperation = 'copy';
        context.drawImage(this._bitmap.canvas, x, y, w, h, 0, 0, w, h);
    
        if (Graphics.canUseSaturationBlend()) {
            var gray = Math.max(0, tone[3]);
            context.globalCompositeOperation = 'saturation';
            context.fillStyle = 'rgba(255,255,255,' + gray / 255 + ')';
            context.fillRect(0, 0, w, h);
        }
    
        var r1 = Math.max(0, tone[0]);
        var g1 = Math.max(0, tone[1]);
        var b1 = Math.max(0, tone[2]);
        context.globalCompositeOperation = 'lighter';
        context.fillStyle = Utils.rgbToCssColor(r1, g1, b1);
        context.fillRect(0, 0, w, h);
    
        if (Graphics.canUseDifferenceBlend()) {
            context.globalCompositeOperation = 'difference';
            context.fillStyle = 'white';
            context.fillRect(0, 0, w, h);
    
            var r2 = Math.max(0, -tone[0]);
            var g2 = Math.max(0, -tone[1]);
            var b2 = Math.max(0, -tone[2]);
            context.globalCompositeOperation = 'lighter';
            context.fillStyle = Utils.rgbToCssColor(r2, g2, b2);
            context.fillRect(0, 0, w, h);
    
            context.globalCompositeOperation = 'difference';
            context.fillStyle = 'white';
            context.fillRect(0, 0, w, h);
        }
    
        var r3 = Math.max(0, color[0]);
        var g3 = Math.max(0, color[1]);
        var b3 = Math.max(0, color[2]);
        var a3 = Math.max(0, color[3]);
        context.globalCompositeOperation = 'source-atop';
        context.fillStyle = Utils.rgbToCssColor(r3, g3, b3);
        context.globalAlpha = a3 / 255;
        context.fillRect(0, 0, w, h);
    
        context.globalCompositeOperation = 'destination-in';
        context.globalAlpha = 1;
        context.drawImage(this._bitmap.canvas, x, y, w, h, 0, 0, w, h);
    };

    _renderCanvas_PIXI:any = PIXI.Sprite.prototype._renderCanvas;
    _renderWebGL_PIXI:any = PIXI.Sprite.prototype._renderWebGL;

   /**
     * @method _renderCanvas
     * @param {Object} renderer
     * @private
     */
    _renderCanvas(renderer: PIXI.CanvasRenderer): void{
        if (this.bitmap) {
            this.bitmap.touch();
        }
        if(this.bitmap && !this.bitmap.isReady()){
            return;
        }
    
        if (this.texture.frame.width > 0 && this.texture.frame.height > 0) {
            this._renderCanvas_PIXI(renderer);
        }
    };
    
    /**
     * checks if we need to speed up custom blendmodes
     * @param renderer
     * @private
     */
    _speedUpCustomBlendModes(renderer: PIXI.CanvasRenderer|any): void {
        var picture = renderer.plugins.picture;
        var blend = this.blendMode;
        if (renderer.renderingToScreen && renderer._activeRenderTarget.root) {
            if (picture.drawModes[blend]) {
                var stage = renderer._lastObjectRendered;
                var f = stage._filters;
                if (!f || !f[0]) {
                    setTimeout(function () {
                        var f = stage._filters;
                        if (!f || !f[0]) {
                            stage.filters = [Sprite.voidFilter];
                            stage.filterArea = new PIXI.Rectangle(0, 0, Graphics.width, Graphics.height);
                        }
                    }, 0);
                }
            }
        }
    };

    /**
     * @method _renderWebGL
     * @param {Object} renderer
     * @private
     */
    _renderWebGL(renderer: PIXI.WebGLRenderer): void {
        if (this.bitmap) {
            this.bitmap.touch();
        }
        if(this.bitmap && !this.bitmap.isReady()){
            return;
        }
        if (this.texture.frame.width > 0 && this.texture.frame.height > 0) {
            if (this._bitmap) {
                this._bitmap.checkDirty();
            }
    
            //copy of pixi-v4 internal code
            this.calculateVertices();
    
            if (this.pluginName === 'sprite' && this._isPicture) {
                // use heavy renderer, which reduces artifacts and applies corrent blendMode,
                // but does not use multitexture optimization
                this._speedUpCustomBlendModes(renderer);
                renderer.setObjectRenderer(renderer.plugins.picture);
                renderer.plugins.picture.render(this);
            } else {
                // use pixi super-speed renderer
                renderer.setObjectRenderer(renderer.plugins[this.pluginName]);
                renderer.plugins[this.pluginName].render(this);
            }
        }
    };

    _refreshFrame: boolean;
    _bitmap: Bitmap;
    _frame: Rectangle;
    _realFrame: Rectangle;
    _offset: Point;
    _blendColor: any;
    _colorTone: number[];
    _canvas: HTMLCanvasElement;
    _context: CanvasRenderingContext2D;
    _tintTexture: PIXI.BaseTexture;
    _isPicture: boolean
    spriteId: number
    opaque: boolean

}

//Sprite.prototype = Object.create(PIXI.Sprite.prototype);
//Sprite.prototype.constructor = Sprite;
 
// The important members from Pixi.js

/**
 * The visibility of the sprite.
 *
 * @property visible
 * @type Boolean
 */

/**
 * The x coordinate of the sprite.
 *
 * @property x
 * @type Number
 */

/**
 * The y coordinate of the sprite.
 *
 * @property y
 * @type Number
 */

/**
 * The origin point of the sprite. (0,0) to (1,1).
 *
 * @property anchor
 * @type Point
 */

/**
 * The scale factor of the sprite.
 *
 * @property scale
 * @type Point
 */

/**
 * The rotation of the sprite in radians.
 *
 * @property rotation
 * @type Number
 */

/**
 * The blend mode to be applied to the sprite.
 *
 * @property blendMode
 * @type Number
 */

/**
 * Sets the filters for the sprite.
 *
 * @property filters
 * @type Array
 */

/**
 * [read-only] The array of children of the sprite.
 *
 * @property children
 * @type Array
 */

/**
 * [read-only] The object that contains the sprite.
 *
 * @property parent
 * @type Object
 */

/**
 * Adds a child to the container.
 *
 * @method addChild
 * @param {Object} child The child to add
 * @return {Object} The child that was added
 */

/**
 * Adds a child to the container at a specified index.
 *
 * @method addChildAt
 * @param {Object} child The child to add
 * @param {Number} index The index to place the child in
 * @return {Object} The child that was added
 */

/**
 * Removes a child from the container.
 *
 * @method removeChild
 * @param {Object} child The child to remove
 * @return {Object} The child that was removed
 */

/**
 * Removes a child from the specified index position.
 *
 * @method removeChildAt
 * @param {Number} index The index to get the child from
 * @return {Object} The child that was removed
 */

//-----------------------------------------------------------------------------
/**
 * The tilemap which displays 2D tile-based game map.
 *
 * @class Tilemap
 * @constructor
 */
class Tilemap extends PIXI.Container {
    constructor(){
        super();
        this.initialize.apply(this, arguments);
    };
    //Tilemap.prototype = Object.create(PIXI.Container.prototype);
    //Tilemap.prototype.constructor = Tilemap;
    initialize(): void{
        PIXI.Container.call(this);
    
        this._margin = 20;
        this._width = Graphics.width + this._margin * 2;
        this._height = Graphics.height + this._margin * 2;
        this._tileWidth = 48;
        this._tileHeight = 48;
        this._mapWidth = 0;
        this._mapHeight = 0;
        this._mapData = null;
        this._layerWidth = 0;
        this._layerHeight = 0;
        this._lastTiles = [];
    
        /**
         * The bitmaps used as a tileset.
         *
         * @property bitmaps
         * @type Array
         */
        this.bitmaps = [];
    
        /**
         * The origin point of the tilemap for scrolling.
         *
         * @property origin
         * @type Point
         */
        this.origin = new Point();
    
        /**
         * The tileset flags.
         *
         * @property flags
         * @type Array
         */
        this.flags = [];
    
        /**
         * The animation count for autotiles.
         *
         * @property animationCount
         * @type Number
         */
        this.animationCount = 0;
    
        /**
         * Whether the tilemap loops horizontal.
         *
         * @property horizontalWrap
         * @type Boolean
         */
        this.horizontalWrap = false;
    
        /**
         * Whether the tilemap loops vertical.
         *
         * @property verticalWrap
         * @type Boolean
         */
        this.verticalWrap = false;
    
        this._createLayers();
        this.refresh();
    };

    /**
     * The width of the screen in pixels.
     *
     * @property width
     * @type Number
     */
    public get width() {
        return this._width;
    }

    public set width(value: number) {
        if (this._width !== value) {
            this._width = value;
            this._createLayers();
        }
    }   

    /**
     * The height of the screen in pixels.
     *
     * @property height
     * @type Number
     */
    public get height() {
        return this._height;
    }

    public set height(value: number) {
        if (this._height !== value) {
            this._height = value;
            this._createLayers();
        }
    }

    /**
     * The width of a tile in pixels.
     *
     * @property tileWidth
     * @type Number
     */
    public get tileWidth() {
        return this._tileWidth;
    }

    public set tileWidth(value: number) {
        if (this._tileWidth !== value) {
            this._tileWidth = value;
            this._createLayers();
        }
    }

    /**
     * The height of a tile in pixels.
     *
     * @property tileHeight
     * @type Number
     */
    public get tileHeight() {
        return this._tileHeight;
    }

    public set tileHeight(value: number) {
        if (this._tileHeight !== value) {
            this._tileHeight = value;
            this._createLayers();
        }
    }   
    
    /**
     * Sets the tilemap data.
     *
     * @method setData
     * @param {Number} width The width of the map in number of tiles
     * @param {Number} height The height of the map in number of tiles
     * @param {Array} data The one dimensional array for the map data
     */
    setData(width: number, height: number, data: number[]): void {
        this._mapWidth = width;
        this._mapHeight = height;
        this._mapData = data;
    };

    /**
     * Checks whether the tileset is ready to render.
     *
     * @method isReady
     * @type Boolean
     * @return {Boolean} True if the tilemap is ready
     */
    isReady(): boolean {
        for (var i = 0; i < this.bitmaps.length; i++) {
            if (this.bitmaps[i] && !this.bitmaps[i].isReady()) {
                return false;
            }
        }
        return true;
    };

    /**
     * Updates the tilemap for each frame.
     *
     * @method update
     */    
    update(): void {
        this.animationCount++;
        this.animationFrame = Math.floor(this.animationCount / 30);
        this.children.forEach(function(child) {
            if (child.update) {
                child.update();
            }
        });
        for (var i=0; i<this.bitmaps.length;i++) {
            if (this.bitmaps[i]) {
                this.bitmaps[i].touch();
            }
        }
    };

    /**
     * Forces to repaint the entire tilemap.
     *
     * @method refresh
     */   
    refresh(): void {
        this._lastTiles.length = 0;
    };

    /**
     * Forces to refresh the tileset
     *
     * @method refresh
     */   
    refreshTileset(): void {

    };

    /**
     * @method updateTransform
     * @private
     */   
    updateTransform(): void {
        var ox = Math.floor(this.origin.x);
        var oy = Math.floor(this.origin.y);
        var startX = Math.floor((ox - this._margin) / this._tileWidth);
        var startY = Math.floor((oy - this._margin) / this._tileHeight);
        this._updateLayerPositions(startX, startY);
        if (this._needsRepaint || this._lastAnimationFrame !== this.animationFrame ||
            this._lastStartX !== startX || this._lastStartY !== startY) {
            this._frameUpdated = this._lastAnimationFrame !== this.animationFrame;
            this._lastAnimationFrame = this.animationFrame;
            this._lastStartX = startX;
            this._lastStartY = startY;
            this._paintAllTiles(startX, startY);
            this._needsRepaint = false;
        }
        this._sortChildren();
        PIXI.Container.prototype.updateTransform.call(this);
    };

    /**
     * @method _createLayers
     * @private
     */
    _createLayers(): void {
        var width = this._width;
        var height = this._height;
        var margin = this._margin;
        var tileCols = Math.ceil(width / this._tileWidth) + 1;
        var tileRows = Math.ceil(height / this._tileHeight) + 1;
        var layerWidth = tileCols * this._tileWidth;
        var layerHeight = tileRows * this._tileHeight;
        this._lowerBitmap = new Bitmap(layerWidth, layerHeight);
        this._upperBitmap = new Bitmap(layerWidth, layerHeight);
        this._layerWidth = layerWidth;
        this._layerHeight = layerHeight;
    
        /*
         * Z coordinate:
         *
         * 0 : Lower tiles
         * 1 : Lower characters
         * 3 : Normal characters
         * 4 : Upper tiles
         * 5 : Upper characters
         * 6 : Airship shadow
         * 7 : Balloon
         * 8 : Animation
         * 9 : Destination
         */
    
        this._lowerLayer = new Sprite();
        this._lowerLayer.move(-margin, -margin, width, height);
        this._lowerLayer.z = 0;
    
        this._upperLayer = new Sprite();
        this._upperLayer.move(-margin, -margin, width, height);
        this._upperLayer.z = 4;
    
        for (var i = 0; i < 4; i++) {
            this._lowerLayer.addChild(new Sprite(this._lowerBitmap));
            this._upperLayer.addChild(new Sprite(this._upperBitmap));
        }
    
        this.addChild(this._lowerLayer);
        this.addChild(this._upperLayer);
    };

   
    /**
     * @method _updateLayerPositions
     * @param {Number} startX
     * @param {Number} startY
     * @private
     */
    _updateLayerPositions(startX: number, startY: number): void {
        var m = this._margin;
        var ox = Math.floor(this.origin.x);
        var oy = Math.floor(this.origin.y);
        var x2 = (ox - m).mod(this._layerWidth);
        var y2 = (oy - m).mod(this._layerHeight);
        var w1 = this._layerWidth - x2;
        var h1 = this._layerHeight - y2;
        var w2 = this._width - w1;
        var h2 = this._height - h1;
    
        for (var i = 0; i < 2; i++) {
            var children;
            if (i === 0) {
                children = this._lowerLayer.children;
            } else {
                children = this._upperLayer.children;
            }
            children[0].move(0, 0, w1, h1);
            children[0].setFrame(x2, y2, w1, h1);
            children[1].move(w1, 0, w2, h1);
            children[1].setFrame(0, y2, w2, h1);
            children[2].move(0, h1, w1, h2);
            children[2].setFrame(x2, 0, w1, h2);
            children[3].move(w1, h1, w2, h2);
            children[3].setFrame(0, 0, w2, h2);
        }
    };
 
    /**
     * @method _paintAllTiles
     * @param {Number} startX
     * @param {Number} startY
     * @private
     */
    _paintAllTiles(startX: number, startY: number): void {
        var tileCols = Math.ceil(this._width / this._tileWidth) + 1;
        var tileRows = Math.ceil(this._height / this._tileHeight) + 1;
        for (var y = 0; y < tileRows; y++) {
            for (var x = 0; x < tileCols; x++) {
                this._paintTiles(startX, startY, x, y);
            }
        }
    };

    /**
     * @method _paintTiles
     * @param {Number} startX
     * @param {Number} startY
     * @param {Number} x
     * @param {Number} y
     * @private
     */
    _paintTiles(startX: number, startY: number, x: number, y: number): void {
        var tableEdgeVirtualId = 10000;
        var mx = startX + x;
        var my = startY + y;
        var dx = (mx * this._tileWidth).mod(this._layerWidth);
        var dy = (my * this._tileHeight).mod(this._layerHeight);
        var lx = dx / this._tileWidth;
        var ly = dy / this._tileHeight;
        var tileId0 = this._readMapData(mx, my, 0);
        var tileId1 = this._readMapData(mx, my, 1);
        var tileId2 = this._readMapData(mx, my, 2);
        var tileId3 = this._readMapData(mx, my, 3);
        var shadowBits = this._readMapData(mx, my, 4);
        var upperTileId1 = this._readMapData(mx, my - 1, 1);
        var lowerTiles = [];
        var upperTiles = [];
    
        if (this._isHigherTile(tileId0)) {
            upperTiles.push(tileId0);
        } else {
            lowerTiles.push(tileId0);
        }
        if (this._isHigherTile(tileId1)) {
            upperTiles.push(tileId1);
        } else {
            lowerTiles.push(tileId1);
        }
    
        lowerTiles.push(-shadowBits);
    
        if (this._isTableTile(upperTileId1) && !this._isTableTile(tileId1)) {
            if (!Tilemap.isShadowingTile(tileId0)) {
                lowerTiles.push(tableEdgeVirtualId + upperTileId1);
            }
        }
    
        if (this._isOverpassPosition(mx, my)) {
            upperTiles.push(tileId2);
            upperTiles.push(tileId3);
        } else {
            if (this._isHigherTile(tileId2)) {
                upperTiles.push(tileId2);
            } else {
                lowerTiles.push(tileId2);
            }
            if (this._isHigherTile(tileId3)) {
                upperTiles.push(tileId3);
            } else {
                lowerTiles.push(tileId3);
            }
        }
    
        var lastLowerTiles = this._readLastTiles(0, lx, ly);
        if (!lowerTiles.equals(lastLowerTiles) ||
                (Tilemap.isTileA1(tileId0) && this._frameUpdated)) {
            this._lowerBitmap.clearRect(dx, dy, this._tileWidth, this._tileHeight);
            for (var i = 0; i < lowerTiles.length; i++) {
                var lowerTileId = lowerTiles[i];
                if (lowerTileId < 0) {
                    this._drawShadow(this._lowerBitmap, shadowBits, dx, dy);
                } else if (lowerTileId >= tableEdgeVirtualId) {
                    this._drawTableEdge(this._lowerBitmap, upperTileId1, dx, dy);
                } else {
                    this._drawTile(this._lowerBitmap, lowerTileId, dx, dy);
                }
            }
            this._writeLastTiles(0, lx, ly, lowerTiles);
        }
    
        var lastUpperTiles = this._readLastTiles(1, lx, ly);
        if (!upperTiles.equals(lastUpperTiles)) {
            this._upperBitmap.clearRect(dx, dy, this._tileWidth, this._tileHeight);
            for (var j = 0; j < upperTiles.length; j++) {
                this._drawTile(this._upperBitmap, upperTiles[j], dx, dy);
            }
            this._writeLastTiles(1, lx, ly, upperTiles);
        }
    };

    /**
     * @method _readLastTiles
     * @param {Number} i
     * @param {Number} x
     * @param {Number} y
     * @private
     */
    _readLastTiles(i: number, x: number, y: number): number[] {
        var array1 = this._lastTiles[i];
        if (array1) {
            var array2 = array1[y];
            if (array2) {
                var tiles = array2[x];
                if (tiles) {
                    return tiles;
                }
            }
        }
        return [];
    };

    /**
     * @method _writeLastTiles
     * @param {Number} i
     * @param {Number} x
     * @param {Number} y
     * @param {Array} tiles
     * @private
     */   
    _writeLastTiles(i: number, x: number, y: number, tiles: number[]): void {
        var array1 = this._lastTiles[i];
        if (!array1) {
            array1 = this._lastTiles[i] = [];
        }
        var array2 = array1[y];
        if (!array2) {
            array2 = array1[y] = [];
        }
        array2[x] = tiles;
    };

    /**
     * @method _drawTile
     * @param {Bitmap} bitmap
     * @param {Number} tileId
     * @param {Number} dx
     * @param {Number} dy
     * @private
     */    
    _drawTile(bitmap: Bitmap, tileId: number, dx: number, dy: number): void {
        if (Tilemap.isVisibleTile(tileId)) {
            if (Tilemap.isAutotile(tileId)) {
                this._drawAutotile(bitmap, tileId, dx, dy);
            } else {
                this._drawNormalTile(bitmap, tileId, dx, dy);
            }
        }
    };

    /**
     * @method _drawNormalTile
     * @param {Bitmap} bitmap
     * @param {Number} tileId
     * @param {Number} dx
     * @param {Number} dy
     * @private
     */   
    _drawNormalTile(bitmap: Bitmap, tileId: number, dx: number, dy: number): void {
        var setNumber = 0;
    
        if (Tilemap.isTileA5(tileId)) {
            setNumber = 4;
        } else {
            setNumber = 5 + Math.floor(tileId / 256);
        }
    
        var w = this._tileWidth;
        var h = this._tileHeight;
        var sx = (Math.floor(tileId / 128) % 2 * 8 + tileId % 8) * w;
        var sy = (Math.floor(tileId % 256 / 8) % 16) * h;
    
        var source = this.bitmaps[setNumber];
        if (source) {
            bitmap.bltImage(source, sx, sy, w, h, dx, dy, w, h);
        }
    };

    /**
     * @method _drawAutotile
     * @param {Bitmap} bitmap
     * @param {Number} tileId
     * @param {Number} dx
     * @param {Number} dy
     * @private
     */    
    _drawAutotile(bitmap: Bitmap, tileId: number, dx: number, dy: number): void {
        var autotileTable = Tilemap.FLOOR_AUTOTILE_TABLE;
        var kind = Tilemap.getAutotileKind(tileId);
        var shape = Tilemap.getAutotileShape(tileId);
        var tx = kind % 8;
        var ty = Math.floor(kind / 8);
        var bx = 0;
        var by = 0;
        var setNumber = 0;
        var isTable = false;
    
        if (Tilemap.isTileA1(tileId)) {
            var waterSurfaceIndex = [0, 1, 2, 1][this.animationFrame % 4];
            setNumber = 0;
            if (kind === 0) {
                bx = waterSurfaceIndex * 2;
                by = 0;
            } else if (kind === 1) {
                bx = waterSurfaceIndex * 2;
                by = 3;
            } else if (kind === 2) {
                bx = 6;
                by = 0;
            } else if (kind === 3) {
                bx = 6;
                by = 3;
            } else {
                bx = Math.floor(tx / 4) * 8;
                by = ty * 6 + Math.floor(tx / 2) % 2 * 3;
                if (kind % 2 === 0) {
                    bx += waterSurfaceIndex * 2;
                }
                else {
                    bx += 6;
                    autotileTable = Tilemap.WATERFALL_AUTOTILE_TABLE;
                    by += this.animationFrame % 3;
                }
            }
        } else if (Tilemap.isTileA2(tileId)) {
            setNumber = 1;
            bx = tx * 2;
            by = (ty - 2) * 3;
            isTable = this._isTableTile(tileId);
        } else if (Tilemap.isTileA3(tileId)) {
            setNumber = 2;
            bx = tx * 2;
            by = (ty - 6) * 2;
            autotileTable = Tilemap.WALL_AUTOTILE_TABLE;
        } else if (Tilemap.isTileA4(tileId)) {
            setNumber = 3;
            bx = tx * 2;
            by = Math.floor((ty - 10) * 2.5 + (ty % 2 === 1 ? 0.5 : 0));
            if (ty % 2 === 1) {
                autotileTable = Tilemap.WALL_AUTOTILE_TABLE;
            }
        }
    
        var table = autotileTable[shape];
        var source = this.bitmaps[setNumber];
    
        if (table && source) {
            var w1 = this._tileWidth / 2;
            var h1 = this._tileHeight / 2;
            for (var i = 0; i < 4; i++) {
                var qsx = table[i][0];
                var qsy = table[i][1];
                var sx1 = (bx * 2 + qsx) * w1;
                var sy1 = (by * 2 + qsy) * h1;
                var dx1 = dx + (i % 2) * w1;
                var dy1 = dy + Math.floor(i / 2) * h1;
                if (isTable && (qsy === 1 || qsy === 5)) {
                    var qsx2 = qsx;
                    var qsy2 = 3;
                    if (qsy === 1) {
                        qsx2 = [0,3,2,1][qsx];
                    }
                    var sx2 = (bx * 2 + qsx2) * w1;
                    var sy2 = (by * 2 + qsy2) * h1;
                    bitmap.bltImage(source, sx2, sy2, w1, h1, dx1, dy1, w1, h1);
                    dy1 += h1/2;
                    bitmap.bltImage(source, sx1, sy1, w1, h1/2, dx1, dy1, w1, h1/2);
                } else {
                    bitmap.bltImage(source, sx1, sy1, w1, h1, dx1, dy1, w1, h1);
                }
            }
        }
    };

    /**
     * @method _drawTableEdge
     * @param {Bitmap} bitmap
     * @param {Number} tileId
     * @param {Number} dx
     * @param {Number} dy
     * @private
     */
    _drawTableEdge(bitmap: Bitmap, tileId: number, dx: number, dy: number): void {
        if (Tilemap.isTileA2(tileId)) {
            var autotileTable = Tilemap.FLOOR_AUTOTILE_TABLE;
            var kind = Tilemap.getAutotileKind(tileId);
            var shape = Tilemap.getAutotileShape(tileId);
            var tx = kind % 8;
            var ty = Math.floor(kind / 8);
            var setNumber = 1;
            var bx = tx * 2;
            var by = (ty - 2) * 3;
            var table = autotileTable[shape];
    
            if (table) {
                var source = this.bitmaps[setNumber];
                var w1 = this._tileWidth / 2;
                var h1 = this._tileHeight / 2;
                for (var i = 0; i < 2; i++) {
                    var qsx = table[2 + i][0];
                    var qsy = table[2 + i][1];
                    var sx1 = (bx * 2 + qsx) * w1;
                    var sy1 = (by * 2 + qsy) * h1 + h1/2;
                    var dx1 = dx + (i % 2) * w1;
                    var dy1 = dy + Math.floor(i / 2) * h1;
                    bitmap.bltImage(source, sx1, sy1, w1, h1/2, dx1, dy1, w1, h1/2);
                }
            }
        }
    };

    /**
     * @method _drawShadow
     * @param {Bitmap} bitmap
     * @param {Number} shadowBits
     * @param {Number} dx
     * @param {Number} dy
     * @private
     */
    _drawShadow(bitmap: Bitmap, shadowBits: number, dx: number, dy: number): void {
        if (shadowBits & 0x0f) {
            var w1 = this._tileWidth / 2;
            var h1 = this._tileHeight / 2;
            var color = 'rgba(0,0,0,0.5)';
            for (var i = 0; i < 4; i++) {
                if (shadowBits & (1 << i)) {
                    var dx1 = dx + (i % 2) * w1;
                    var dy1 = dy + Math.floor(i / 2) * h1;
                    bitmap.fillRect(dx1, dy1, w1, h1, color);
                }
            }
        }
    };

    /**
     * @method _readMapData
     * @param {Number} x
     * @param {Number} y
     * @param {Number} z
     * @return {Number}
     * @private
     */   
    _readMapData(x: number, y: number, z: number): number {
        if (this._mapData) {
            var width = this._mapWidth;
            var height = this._mapHeight;
            if (this.horizontalWrap) {
                x = x.mod(width);
            }
            if (this.verticalWrap) {
                y = y.mod(height);
            }
            if (x >= 0 && x < width && y >= 0 && y < height) {
                return this._mapData[(z * height + y) * width + x] || 0;
            } else {
                return 0;
            }
        } else {
            return 0;
        }
    };

    /**
     * @method _isHigherTile
     * @param {Number} tileId
     * @return {Boolean}
     * @private
     */   
    _isHigherTile(tileId: number): any {
        return this.flags[tileId] & 0x10;
    };
    
    /**
     * @method _isTableTile
     * @param {Number} tileId
     * @return {Boolean}
     * @private
     */
    _isTableTile(tileId: number): any {
        return Tilemap.isTileA2(tileId) && (this.flags[tileId] & 0x80);
    };


    /**
     * @method _isOverpassPosition
     * @param {Number} mx
     * @param {Number} my
     * @return {Boolean}
     * @private
     */
    _isOverpassPosition(mx: number, my: number): boolean {
        return false;
    };

    /**
     * @method _sortChildren
     * @private
     */   
    _sortChildren(): void {
        this.children.sort(this._compareChildOrder.bind(this));
    };

    /**
     * @method _compareChildOrder
     * @param {Object} a
     * @param {Object} b
     * @private
     */   
    _compareChildOrder(a: Sprite, b: Sprite): number {
        if (a.z !== b.z) {
            return a.z - b.z;
        } else if (a.y !== b.y) {
            return a.y - b.y;
        } else {
            return a.spriteId - b.spriteId;
        }
    };

    // Tile type checkers

    static TILE_ID_B = 0;
    static TILE_ID_C = 256;
    static TILE_ID_D = 512;
    static TILE_ID_E = 768;
    static TILE_ID_A5 = 1536;
    static TILE_ID_A1 = 2048;
    static TILE_ID_A2 = 2816;
    static TILE_ID_A3 = 4352;
    static TILE_ID_A4 = 5888;
    static TILE_ID_MAX = 8192;

    static isVisibleTile(tileId: number): boolean {
        return tileId > 0 && tileId < this.TILE_ID_MAX;
    };

    static isAutotile(tileId: number): boolean {
        return tileId >= this.TILE_ID_A1;
    };

    static getAutotileKind(tileId: number): number {
        return Math.floor((tileId - this.TILE_ID_A1) / 48);
    };

    static getAutotileShape(tileId: number): number {
        return (tileId - this.TILE_ID_A1) % 48;
    };

    static makeAutotileId(kind: number, shape: number): number {
        return this.TILE_ID_A1 + kind * 48 + shape;
    };

    static isSameKindTile(tileID1: number, tileID2: number): boolean {
        if (this.isAutotile(tileID1) && this.isAutotile(tileID2)) {
            return this.getAutotileKind(tileID1) === this.getAutotileKind(tileID2);
        } else {
            return tileID1 === tileID2;
        }
    };

    static isTileA1(tileId: number): boolean {
        return tileId >= this.TILE_ID_A1 && tileId < this.TILE_ID_A2;
    };

    static isTileA2(tileId: number): boolean {
        return tileId >= this.TILE_ID_A2 && tileId < this.TILE_ID_A3;
    };

    static isTileA3(tileId: number): boolean {
        return tileId >= this.TILE_ID_A3 && tileId < this.TILE_ID_A4;
    };

    static isTileA4(tileId: number): boolean {
        return tileId >= this.TILE_ID_A4 && tileId < this.TILE_ID_MAX;
    };

    static isTileA5(tileId: number): boolean {
        return tileId >= this.TILE_ID_A5 && tileId < this.TILE_ID_A1;
    };

    static isWaterTile(tileId: number): boolean {
        if (this.isTileA1(tileId)) {
            return !(tileId >= this.TILE_ID_A1 + 96 && tileId < this.TILE_ID_A1 + 192);
        } else {
            return false;
        }
    };

    static isWaterfallTile(tileId: number): boolean {
        if (tileId >= this.TILE_ID_A1 + 192 && tileId < this.TILE_ID_A2) {
            return this.getAutotileKind(tileId) % 2 === 1;
        } else {
            return false;
        }
    };

    static isGroundTile(tileId: number): boolean {
        return this.isTileA1(tileId) || this.isTileA2(tileId) || this.isTileA5(tileId);
    };

    static isShadowingTile(tileId: number): boolean {
        return this.isTileA3(tileId) || this.isTileA4(tileId);
    };

    static isRoofTile(tileId: number): boolean {
        return this.isTileA3(tileId) && this.getAutotileKind(tileId) % 16 < 8;
    };

    static isWallTopTile(tileId: number): boolean {
        return this.isTileA4(tileId) && this.getAutotileKind(tileId) % 16 < 8;
    };

    static isWallSideTile(tileId: number): boolean {
        return (this.isTileA3(tileId) || this.isTileA4(tileId)) &&
                this.getAutotileKind(tileId) % 16 >= 8;
    };

    static isWallTile(tileId: number): boolean {
        return this.isWallTopTile(tileId) || this.isWallSideTile(tileId);
    };

    static isFloorTypeAutotile(tileId: number): boolean {
        return (this.isTileA1(tileId) && !this.isWaterfallTile(tileId)) ||
                this.isTileA2(tileId) || this.isWallTopTile(tileId);
    };

    static isWallTypeAutotile(tileId: number): boolean {
        return this.isRoofTile(tileId) || this.isWallSideTile(tileId);
    };

    static isWaterfallTypeAutotile(tileId: number): boolean {
        return this.isWaterfallTile(tileId);
    };

    static FLOOR_AUTOTILE_TABLE = [
        [[2,4],[1,4],[2,3],[1,3]],[[2,0],[1,4],[2,3],[1,3]],
        [[2,4],[3,0],[2,3],[1,3]],[[2,0],[3,0],[2,3],[1,3]],
        [[2,4],[1,4],[2,3],[3,1]],[[2,0],[1,4],[2,3],[3,1]],
        [[2,4],[3,0],[2,3],[3,1]],[[2,0],[3,0],[2,3],[3,1]],
        [[2,4],[1,4],[2,1],[1,3]],[[2,0],[1,4],[2,1],[1,3]],
        [[2,4],[3,0],[2,1],[1,3]],[[2,0],[3,0],[2,1],[1,3]],
        [[2,4],[1,4],[2,1],[3,1]],[[2,0],[1,4],[2,1],[3,1]],
        [[2,4],[3,0],[2,1],[3,1]],[[2,0],[3,0],[2,1],[3,1]],
        [[0,4],[1,4],[0,3],[1,3]],[[0,4],[3,0],[0,3],[1,3]],
        [[0,4],[1,4],[0,3],[3,1]],[[0,4],[3,0],[0,3],[3,1]],
        [[2,2],[1,2],[2,3],[1,3]],[[2,2],[1,2],[2,3],[3,1]],
        [[2,2],[1,2],[2,1],[1,3]],[[2,2],[1,2],[2,1],[3,1]],
        [[2,4],[3,4],[2,3],[3,3]],[[2,4],[3,4],[2,1],[3,3]],
        [[2,0],[3,4],[2,3],[3,3]],[[2,0],[3,4],[2,1],[3,3]],
        [[2,4],[1,4],[2,5],[1,5]],[[2,0],[1,4],[2,5],[1,5]],
        [[2,4],[3,0],[2,5],[1,5]],[[2,0],[3,0],[2,5],[1,5]],
        [[0,4],[3,4],[0,3],[3,3]],[[2,2],[1,2],[2,5],[1,5]],
        [[0,2],[1,2],[0,3],[1,3]],[[0,2],[1,2],[0,3],[3,1]],
        [[2,2],[3,2],[2,3],[3,3]],[[2,2],[3,2],[2,1],[3,3]],
        [[2,4],[3,4],[2,5],[3,5]],[[2,0],[3,4],[2,5],[3,5]],
        [[0,4],[1,4],[0,5],[1,5]],[[0,4],[3,0],[0,5],[1,5]],
        [[0,2],[3,2],[0,3],[3,3]],[[0,2],[1,2],[0,5],[1,5]],
        [[0,4],[3,4],[0,5],[3,5]],[[2,2],[3,2],[2,5],[3,5]],
        [[0,2],[3,2],[0,5],[3,5]],[[0,0],[1,0],[0,1],[1,1]]
    ];
    static WALL_AUTOTILE_TABLE = [
        [[2,2],[1,2],[2,1],[1,1]],[[0,2],[1,2],[0,1],[1,1]],
        [[2,0],[1,0],[2,1],[1,1]],[[0,0],[1,0],[0,1],[1,1]],
        [[2,2],[3,2],[2,1],[3,1]],[[0,2],[3,2],[0,1],[3,1]],
        [[2,0],[3,0],[2,1],[3,1]],[[0,0],[3,0],[0,1],[3,1]],
        [[2,2],[1,2],[2,3],[1,3]],[[0,2],[1,2],[0,3],[1,3]],
        [[2,0],[1,0],[2,3],[1,3]],[[0,0],[1,0],[0,3],[1,3]],
        [[2,2],[3,2],[2,3],[3,3]],[[0,2],[3,2],[0,3],[3,3]],
        [[2,0],[3,0],[2,3],[3,3]],[[0,0],[3,0],[0,3],[3,3]]
    ];
    static WATERFALL_AUTOTILE_TABLE = [
        [[2,0],[1,0],[2,1],[1,1]],[[0,0],[1,0],[0,1],[1,1]],
        [[2,0],[3,0],[2,1],[3,1]],[[0,0],[3,0],[0,1],[3,1]]
    ];

    _margin: number;    
    _width: number;
    _height: number;
    _tileWidth: number;
    _tileHeight: number;
    _mapWidth: number;
    _mapHeight: number;
    _mapData: number[];
    _layerWidth: number;
    _layerHeight: number;
    _lastTiles: number[][][][];

    bitmaps: Bitmap[];
    origin: Point;
    //flags: { [key: number]: boolean };
    flags: any;
    animationCount: number;
    animationFrame: number;
    horizontalWrap: boolean;
    verticalWrap: boolean;    
    _needsRepaint: boolean;
    _lastAnimationFrame: number;
    _lastStartX: number;
    _lastStartY: number;
    _frameUpdated: boolean;
    _lowerBitmap: Bitmap;
    _upperBitmap: Bitmap;
    _lowerLayer: Sprite;
    _upperLayer: Sprite;
}

// The important members from Pixi.js

/**
 * [read-only] The array of children of the tilemap.
 *
 * @property children
 * @type Array
 */

/**
 * [read-only] The object that contains the tilemap.
 *
 * @property parent
 * @type Object
 */

/**
 * Adds a child to the container.
 *
 * @method addChild
 * @param {Object} child The child to add
 * @return {Object} The child that was added
 */

/**
 * Adds a child to the container at a specified index.
 *
 * @method addChildAt
 * @param {Object} child The child to add
 * @param {Number} index The index to place the child in
 * @return {Object} The child that was added
 */

/**
 * Removes a child from the container.
 *
 * @method removeChild
 * @param {Object} child The child to remove
 * @return {Object} The child that was removed
 */

/**
 * Removes a child from the specified index position.
 *
 * @method removeChildAt
 * @param {Number} index The index to get the child from
 * @return {Object} The child that was removed
 */

//-----------------------------------------------------------------------------
/**
 * The tilemap which displays 2D tile-based game map using shaders
 *
 * @class Tilemap
 * @constructor
 */

// we need this constant for some platforms (Samsung S4, S5, Tab4, HTC One H8)
PIXI.glCore.VertexArrayObject.FORCE_NATIVE = true;
PIXI.settings.GC_MODE = PIXI.GC_MODES.AUTO;
PIXI.tilemap.TileRenderer.SCALE_MODE = PIXI.SCALE_MODES.NEAREST;
PIXI.tilemap.TileRenderer.DO_CLEAR = true;


class ShaderTilemap extends Tilemap {
    constructor(){
        super();
        Tilemap.apply(this, arguments);
        this.roundPixels = true;
    }
 

    //ShaderTilemap.prototype = Object.create(Tilemap.prototype);
    //ShaderTilemap.prototype.constructor = ShaderTilemap;

    
   
    /**
     * Uploads animation state in renderer
     *
     * @method _hackRenderer
     * @private
     */
    _hackRenderer(renderer: PIXI.WebGLRenderer | PIXI.CanvasRenderer): PIXI.WebGLRenderer | PIXI.CanvasRenderer {
        var af = this.animationFrame % 4;
        if (af==3) af = 1;
        renderer.plugins.tilemap.tileAnim[0] = af * this._tileWidth;
        renderer.plugins.tilemap.tileAnim[1] = (this.animationFrame % 3) * this._tileHeight;
        return renderer;
    };

    /**
     * PIXI render method
     *
     * @method renderCanvas
     * @param {Object} pixi renderer
     */
    renderCanvas(renderer: PIXI.CanvasRenderer): void {
        this._hackRenderer(renderer);
        PIXI.Container.prototype.renderCanvas.call(this, renderer);
    };

    /**
     * PIXI render method
     *
     * @method renderWebGL
     * @param {Object} pixi renderer
     */   
    renderWebGL(renderer: PIXI.WebGLRenderer): void {
        this._hackRenderer(renderer);
        PIXI.Container.prototype.renderWebGL.call(this, renderer);
    };

    /**
     * Forces to repaint the entire tilemap AND update bitmaps list if needed
     *
     * @method refresh
     */   
    refresh(): void {
        if (this._lastBitmapLength !== this.bitmaps.length) {
            this._lastBitmapLength = this.bitmaps.length;
            this.refreshTileset();
        };
        this._needsRepaint = true;
    };
    
    /**
     * Call after you update tileset
     *
     * @method updateBitmaps
     */
    refreshTileset(): void {
        var bitmaps = this.bitmaps.map(function(x) { return x._baseTexture ? new PIXI.Texture(x._baseTexture) : x; } );
        this.lowerLayer.setBitmaps(bitmaps);
        this.upperLayer.setBitmaps(bitmaps);
    };
   
    /**
     * @method updateTransform
     * @private
     */
    updateTransform(): void {
        if (this.roundPixels) {
            var ox = Math.floor(this.origin.x);
            var oy = Math.floor(this.origin.y);
        } else {
            ox = this.origin.x;
            oy = this.origin.y;
        }
        var startX = Math.floor((ox - this._margin) / this._tileWidth);
        var startY = Math.floor((oy - this._margin) / this._tileHeight);
        this._updateLayerPositions(startX, startY);
        if (this._needsRepaint ||
            this._lastStartX !== startX || this._lastStartY !== startY) {
            this._lastStartX = startX;
            this._lastStartY = startY;
            this._paintAllTiles(startX, startY);
            this._needsRepaint = false;
        }
        this._sortChildren();
        PIXI.Container.prototype.updateTransform.call(this);
    };

    /**
     * @method _createLayers
     * @private
     */
    _createLayers(): void {
        var width = this._width;
        var height = this._height;
        var margin = this._margin;
        var tileCols = Math.ceil(width / this._tileWidth) + 1;
        var tileRows = Math.ceil(height / this._tileHeight) + 1;
        var layerWidth = this._layerWidth = tileCols * this._tileWidth;
        var layerHeight = this._layerHeight = tileRows * this._tileHeight;
        this._needsRepaint = true;
    
        if (!this.lowerZLayer) {
            //@hackerham: create layers only in initialization. Doesn't depend on width/height
            this.addChild(this.lowerZLayer = new PIXI.tilemap.ZLayer(this, 0));
            this.addChild(this.upperZLayer = new PIXI.tilemap.ZLayer(this, 4));
    
            var parameters = PluginManager.parameters('ShaderTilemap');
            var useSquareShader = Number(parameters.hasOwnProperty('squareShader') ? parameters['squareShader'] : 0);
    
            this.lowerZLayer.addChild(this.lowerLayer = new PIXI.tilemap.CompositeRectTileLayer(0, [], useSquareShader));
            this.lowerLayer.shadowColor = new Float32Array([0.0, 0.0, 0.0, 0.5]);
            this.upperZLayer.addChild(this.upperLayer = new PIXI.tilemap.CompositeRectTileLayer(4, [], useSquareShader));
        }
    };
   
    /**
     * @method _updateLayerPositions
     * @param {Number} startX
     * @param {Number} startY
     * @private
     */
    _updateLayerPositions(startX: number, startY: number): void {
        if (this.roundPixels) {
            var ox = Math.floor(this.origin.x);
            var oy = Math.floor(this.origin.y);
        } else {
            ox = this.origin.x;
            oy = this.origin.y;
        }
        this.lowerZLayer.position.x = startX * this._tileWidth - ox;
        this.lowerZLayer.position.y = startY * this._tileHeight - oy;
        this.upperZLayer.position.x = startX * this._tileWidth - ox;
        this.upperZLayer.position.y = startY * this._tileHeight - oy;
    };

    /**
     * @method _paintAllTiles
     * @param {Number} startX
     * @param {Number} startY
     * @private
     */   
    _paintAllTiles(startX: number, startY: number): void {
        this.lowerZLayer.clear();
        this.upperZLayer.clear();
        var tileCols = Math.ceil(this._width / this._tileWidth) + 1;
        var tileRows = Math.ceil(this._height / this._tileHeight) + 1;
        for (var y = 0; y < tileRows; y++) {
            for (var x = 0; x < tileCols; x++) {
                this._paintTiles(startX, startY, x, y);
            }
        }
    };

    /**
     * @method _paintTiles
     * @param {Number} startX
     * @param {Number} startY
     * @param {Number} x
     * @param {Number} y
     * @private
     */   
    _paintTiles(startX: number, startY: number, x: number, y: number): void {
        var mx = startX + x;
        var my = startY + y;
        var dx = x * this._tileWidth, dy = y * this._tileHeight;
        var tileId0 = this._readMapData(mx, my, 0);
        var tileId1 = this._readMapData(mx, my, 1);
        var tileId2 = this._readMapData(mx, my, 2);
        var tileId3 = this._readMapData(mx, my, 3);
        var shadowBits = this._readMapData(mx, my, 4);
        var upperTileId1 = this._readMapData(mx, my - 1, 1);
        var lowerLayer = this.lowerLayer.children[0];
        var upperLayer = this.upperLayer.children[0];
    
        if (this._isHigherTile(tileId0)) {
            this._drawTile(upperLayer, tileId0, dx, dy);
        } else {
            this._drawTile(lowerLayer, tileId0, dx, dy);
        }
        if (this._isHigherTile(tileId1)) {
            this._drawTile(upperLayer, tileId1, dx, dy);
        } else {
            this._drawTile(lowerLayer, tileId1, dx, dy);
        }
    
        this._drawShadow(lowerLayer, shadowBits, dx, dy);
        if (this._isTableTile(upperTileId1) && !this._isTableTile(tileId1)) {
            if (!Tilemap.isShadowingTile(tileId0)) {
                this._drawTableEdge(lowerLayer, upperTileId1, dx, dy);
            }
        }
    
        if (this._isOverpassPosition(mx, my)) {
            this._drawTile(upperLayer, tileId2, dx, dy);
            this._drawTile(upperLayer, tileId3, dx, dy);
        } else {
            if (this._isHigherTile(tileId2)) {
                this._drawTile(upperLayer, tileId2, dx, dy);
            } else {
                this._drawTile(lowerLayer, tileId2, dx, dy);
            }
            if (this._isHigherTile(tileId3)) {
                this._drawTile(upperLayer, tileId3, dx, dy);
            } else {
                this._drawTile(lowerLayer, tileId3, dx, dy);
            }
        }
    };

    /**
     * @method _drawTile
     * @param {Array} layers
     * @param {Number} tileId
     * @param {Number} dx
     * @param {Number} dy
     * @private
     */   
    _drawTile(layer: any, tileId: number, dx: number, dy: number): void {
        if (Tilemap.isVisibleTile(tileId)) {
            if (Tilemap.isAutotile(tileId)) {
                this._drawAutotile(layer, tileId, dx, dy);
            } else {
                this._drawNormalTile(layer, tileId, dx, dy);
            }
        }
    };

    //_drawTile(layer: PIXI.tilemap.RectTileLayer, tileId: number, dx: number, dy: number): void{};
    //_drawNormalTile(bitmap: Bitmap, tileId: number, dx: number, dy: number): void{};
 
    /**
     * @method _drawNormalTile
     * @param {Array} layers
     * @param {Number} tileId
     * @param {Number} dx
     * @param {Number} dy
     * @private
     */
    _drawNormalTile(layer: any, tileId: number, dx: number, dy: number): void {
        var setNumber = 0;
    
        if (Tilemap.isTileA5(tileId)) {
            setNumber = 4;
        } else {
            setNumber = 5 + Math.floor(tileId / 256);
        }
    
        var w = this._tileWidth;
        var h = this._tileHeight;
        var sx = (Math.floor(tileId / 128) % 2 * 8 + tileId % 8) * w;
        var sy = (Math.floor(tileId % 256 / 8) % 16) * h;
    
        layer.addRect(setNumber, sx, sy, dx, dy, w, h);
    };

    /**
     * @method _drawAutotile
     * @param {Array} layers
     * @param {Number} tileId
     * @param {Number} dx
     * @param {Number} dy
     * @private
     */
    _drawAutotile(layer: any, tileId: number, dx: number, dy: number): void {
        var autotileTable = Tilemap.FLOOR_AUTOTILE_TABLE;
        var kind = Tilemap.getAutotileKind(tileId);
        var shape = Tilemap.getAutotileShape(tileId);
        var tx = kind % 8;
        var ty = Math.floor(kind / 8);
        var bx = 0;
        var by = 0;
        var setNumber = 0;
        var isTable = false;
        var animX = 0, animY = 0;
    
        if (Tilemap.isTileA1(tileId)) {
            setNumber = 0;
            if (kind === 0) {
                animX = 2;
                by = 0;
            } else if (kind === 1) {
                animX = 2;
                by = 3;
            } else if (kind === 2) {
                bx = 6;
                by = 0;
            } else if (kind === 3) {
                bx = 6;
                by = 3;
            } else {
                bx = Math.floor(tx / 4) * 8;
                by = ty * 6 + Math.floor(tx / 2) % 2 * 3;
                if (kind % 2 === 0) {
                    animX = 2;
                }
                else {
                    bx += 6;
                    autotileTable = Tilemap.WATERFALL_AUTOTILE_TABLE;
                    animY = 1;
                }
            }
        } else if (Tilemap.isTileA2(tileId)) {
            setNumber = 1;
            bx = tx * 2;
            by = (ty - 2) * 3;
            isTable = this._isTableTile(tileId);
        } else if (Tilemap.isTileA3(tileId)) {
            setNumber = 2;
            bx = tx * 2;
            by = (ty - 6) * 2;
            autotileTable = Tilemap.WALL_AUTOTILE_TABLE;
        } else if (Tilemap.isTileA4(tileId)) {
            setNumber = 3;
            bx = tx * 2;
            by = Math.floor((ty - 10) * 2.5 + (ty % 2 === 1 ? 0.5 : 0));
            if (ty % 2 === 1) {
                autotileTable = Tilemap.WALL_AUTOTILE_TABLE;
            }
        }
    
        var table = autotileTable[shape];
        var w1 = this._tileWidth / 2;
        var h1 = this._tileHeight / 2;
        for (var i = 0; i < 4; i++) {
            var qsx = table[i][0];
            var qsy = table[i][1];
            var sx1 = (bx * 2 + qsx) * w1;
            var sy1 = (by * 2 + qsy) * h1;
            var dx1 = dx + (i % 2) * w1;
            var dy1 = dy + Math.floor(i / 2) * h1;
            if (isTable && (qsy === 1 || qsy === 5)) {
                var qsx2 = qsx;
                var qsy2 = 3;
                if (qsy === 1) {
                    //qsx2 = [0, 3, 2, 1][qsx];
                    qsx2 = (4-qsx)%4;
                }
                var sx2 = (bx * 2 + qsx2) * w1;
                var sy2 = (by * 2 + qsy2) * h1;
                layer.addRect(setNumber, sx2, sy2, dx1, dy1, w1, h1, animX, animY);
                layer.addRect(setNumber, sx1, sy1, dx1, dy1+h1/2, w1, h1/2, animX, animY);
            } else {
                layer.addRect(setNumber, sx1, sy1, dx1, dy1, w1, h1, animX, animY);
            }
        }
    };
   
    /**
     * @method _drawTableEdge
     * @param {Array} layers
     * @param {Number} tileId
     * @param {Number} dx
     * @param {Number} dy
     * @private
     */  
    _drawTableEdge(layer: any, tileId: number, dx: number, dy: number): void {
        if (Tilemap.isTileA2(tileId)) {
            var autotileTable = Tilemap.FLOOR_AUTOTILE_TABLE;
            var kind = Tilemap.getAutotileKind(tileId);
            var shape = Tilemap.getAutotileShape(tileId);
            var tx = kind % 8;
            var ty = Math.floor(kind / 8);
            var setNumber = 1;
            var bx = tx * 2;
            var by = (ty - 2) * 3;
            var table = autotileTable[shape];
            var w1 = this._tileWidth / 2;
            var h1 = this._tileHeight / 2;
            for (var i = 0; i < 2; i++) {
                var qsx = table[2 + i][0];
                var qsy = table[2 + i][1];
                var sx1 = (bx * 2 + qsx) * w1;
                var sy1 = (by * 2 + qsy) * h1 + h1 / 2;
                var dx1 = dx + (i % 2) * w1;
                var dy1 = dy + Math.floor(i / 2) * h1;
                layer.addRect(setNumber, sx1, sy1, dx1, dy1, w1, h1/2);
            }
        }
    };
    
    /**
     * @method _drawShadow
     * @param {Number} shadowBits
     * @param {Number} dx
     * @param {Number} dy
     * @private
     */
    _drawShadow(layer: any, shadowBits: number, dx: number, dy: number): void {
        if (shadowBits & 0x0f) {
            var w1 = this._tileWidth / 2;
            var h1 = this._tileHeight / 2;
            for (var i = 0; i < 4; i++) {
                if (shadowBits & (1 << i)) {
                    var dx1 = dx + (i % 2) * w1;
                    var dy1 = dy + Math.floor(i / 2) * h1;
                    layer.addRect(-1, 0, 0, dx1, dy1, w1, h1);
                }
            }
        }
    };

    //FIXME 扩展
    lowerLayer:any;
    upperLayer:any;

    roundPixels: boolean;
    _lastBitmapLength: number;
    lowerZLayer: PIXI.tilemap.ZLayer;
    upperZLayer: PIXI.tilemap.ZLayer;

}
ShaderTilemap.prototype = Object.create(Tilemap.prototype);
ShaderTilemap.prototype.constructor = ShaderTilemap;

//-----------------------------------------------------------------------------
/**
 * The sprite object for a tiling image.
 *
 * @class TilingSprite
 * @constructor
 * @param {Bitmap} bitmap The image for the tiling sprite
 */
class TilingSprite {
    constructor(){
        this.initialize.apply(this, arguments);
    };

    //TilingSprite.prototype = Object.create(PIXI.extras.PictureTilingSprite.prototype);
    //TilingSprite.prototype.constructor = TilingSprite;

    initialize(bitmap: Bitmap): void {
        var texture = new PIXI.Texture(new PIXI.BaseTexture());
    
        PIXI.extras.PictureTilingSprite.call(this, texture);
    
        this._bitmap = null;
        this._width = 0;
        this._height = 0;
        this._frame = new Rectangle();
        this.spriteId = Sprite._counter++;
        /**
         * The origin point of the tiling sprite for scrolling.
         *
         * @property origin
         * @type Point
         */
        this.origin = new Point();
    
        this.bitmap = bitmap;
    };

     _renderCanvas_PIXI = PIXI.extras.PictureTilingSprite.prototype._renderCanvas;
     _renderWebGL_PIXI = PIXI.extras.PictureTilingSprite.prototype._renderWebGL;


    /**
     * @method _renderCanvas
     * @param {Object} renderer
     * @private
     */
    _renderCanvas(renderer: PIXI.CanvasRenderer): void {
        if (this._bitmap) {
            this._bitmap.touch();
        }
        if (this.texture.frame.width > 0 && this.texture.frame.height > 0) {
            this._renderCanvas_PIXI(renderer);
        }
    };

    /**
     * @method _renderWebGL
     * @param {Object} renderer
     * @private
     */
    _renderWebGL(renderer: PIXI.WebGLRenderer): void {
        if (this._bitmap) {
            this._bitmap.touch();
        }
        if (this.texture.frame.width > 0 && this.texture.frame.height > 0) {
            if (this._bitmap) {
                this._bitmap.checkDirty();
            }
            this._renderWebGL_PIXI(renderer);
        }
    };

    /**
     * The image for the tiling sprite.
     *
     * @property bitmap
     * @type Bitmap
     */
    public get bitmap() {
        return this._bitmap;
    }

    public set bitmap(value: Bitmap) {
        if (this._bitmap !== value) {
            this._bitmap = value;
            if (this._bitmap) {
                this._bitmap.addLoadListener(this._onBitmapLoad.bind(this));
            } else {
                this.texture.frame = Rectangle.emptyRectangle;
            }
        }
    }

    /**
     * The opacity of the tiling sprite (0 to 255).
     *
     * @property opacity
     * @type Number
     */
    public get opacity() {
        return this.alpha * 255;
    }

    public set opacity(value: number) {
        this.alpha = value.clamp(0, 255) / 255;
    }

    /**
     * Updates the tiling sprite for each frame.
     *
     * @method update
     */

    update(): void {
        this.children.forEach(function(child:any) {
            if (child.update) {
                child.update();
            }
        });
    };

    /**
     * Sets the x, y, width, and height all at once.
     *
     * @method move
     * @param {Number} x The x coordinate of the tiling sprite
     * @param {Number} y The y coordinate of the tiling sprite
     * @param {Number} width The width of the tiling sprite
     * @param {Number} height The height of the tiling sprite
     */
    move(x?: number, y?: number, width?: number, height?: number): void {
        this.x = x || 0;
        this.y = y || 0;
        this._width = width || 0;
        this._height = height || 0;
    };
    /**
     * Specifies the region of the image that the tiling sprite will use.
     *
     * @method setFrame
     * @param {Number} x The x coordinate of the frame
     * @param {Number} y The y coordinate of the frame
     * @param {Number} width The width of the frame
     * @param {Number} height The height of the frame
     */
    setFrame(x: number, y: number, width: number, height: number): void {
        this._frame.x = x;
        this._frame.y = y;
        this._frame.width = width;
        this._frame.height = height;
        this._refresh();
    };
   
    /**
     * @method updateTransform
     * @private
     */
    updateTransform(): void {
        this.tilePosition.x = Math.round(-this.origin.x);
        this.tilePosition.y = Math.round(-this.origin.y);
        this.updateTransformTS();
    };
    updateTransformTS = PIXI.extras.TilingSprite.prototype.updateTransform;

   
    /**
     * @method _onBitmapLoad
     * @private
     */
    _onBitmapLoad(): void {
        this.texture.baseTexture = this._bitmap.baseTexture;
        this._refresh();
    };

    /**
     * @method _refresh
     * @private
     */
    _refresh(): void {
        var frame = this._frame.clone();
        if (frame.width === 0 && frame.height === 0 && this._bitmap) {
            frame.width = this._bitmap.width;
            frame.height = this._bitmap.height;
        }
        this.texture.frame = frame;
        this.texture._updateID++;
        this.tilingTexture = null;
    };
  _speedUpCustomBlendModes = Sprite.prototype._speedUpCustomBlendModes;


    x:number;
    y:number;
    alpha:any;
    texture:any;
    children:any;
    tilingTexture:any;
    tilePosition:any;
    //bitmap: Bitmap;
    //opacity: number;
    _bitmap: Bitmap;
    _width: number;
    _height: number;
    _frame: Rectangle;
    spriteId: number;
    origin: Point;
}

TilingSprite.prototype = Object.create(PIXI.extras.PictureTilingSprite.prototype);
TilingSprite.prototype.constructor = TilingSprite;

TilingSprite.prototype.initialize = function(bitmap) {
    var texture = new PIXI.Texture(new PIXI.BaseTexture());

    PIXI.extras.PictureTilingSprite.call(this, texture);

    this._bitmap = null;
    this._width = 0;
    this._height = 0;
    this._frame = new Rectangle();
    this.spriteId = Sprite._counter++;
    /**
     * The origin point of the tiling sprite for scrolling.
     *
     * @property origin
     * @type Point
     */
    this.origin = new Point();

    this.bitmap = bitmap;
};

/**
 * Sets the x, y, width, and height all at once.
 *
 * @method move
 * @param {Number} x The x coordinate of the tiling sprite
 * @param {Number} y The y coordinate of the tiling sprite
 * @param {Number} width The width of the tiling sprite
 * @param {Number} height The height of the tiling sprite
 */
TilingSprite.prototype.move = function(x, y, width, height) {
    this.x = x || 0;
    this.y = y || 0;
    this._width = width || 0;
    this._height = height || 0;
};

/**
 * @method _renderWebGL
 * @param {Object} renderer
 * @private
 */
TilingSprite.prototype._renderWebGL = function(renderer) {
    if (this._bitmap) {
        this._bitmap.touch();
        this._bitmap.checkDirty();
    }

    this._speedUpCustomBlendModes(renderer);

    this._renderWebGL_PIXI(renderer);
};
 
// The important members from Pixi.js

/**
 * The visibility of the tiling sprite.
 *
 * @property visible
 * @type Boolean
 */

/**
 * The x coordinate of the tiling sprite.
 *
 * @property x
 * @type Number
 */

/**
 * The y coordinate of the tiling sprite.
 *
 * @property y
 * @type Number
 */

//-----------------------------------------------------------------------------
/**
 * The sprite which covers the entire game screen.
 *
 * @class ScreenSprite
 * @constructor
 */
class ScreenSprite extends PIXI.Container {
    constructor(){
        super();
        this.initialize.apply(this, arguments);
    };

    initialize(): void {
        PIXI.Container.call(this);
    
        this._graphics = new PIXI.Graphics();
        this.addChild(this._graphics);
        this.opacity = 0;
    
        this._red = -1;
        this._green = -1;
        this._blue = -1;
        this._colorText = '';
        this.setBlack();
    };

    /**
     * The opacity of the sprite (0 to 255).
     *
     * @property opacity
     * @type Number
     */
    public get opacity() {
        return this.alpha * 255;
    }
    
    public set opacity(value: number) {
        this.alpha = value.clamp(0, 255) / 255;
    }

    static YEPWarned: boolean = false;
    static warnYep(): void {
        if (!ScreenSprite.YEPWarned) {
            console.log("Deprecation warning. Please update YEP_CoreEngine. ScreenSprite is not a sprite, it has graphics inside.");
            ScreenSprite.YEPWarned = true;
        }
    };

    public get anchor() {
        ScreenSprite.warnYep();
        this.scale.x = 1;
        this.scale.y = 1;
        return {x: 0, y: 0};
    }
    
    public set anchor(value: any) {
        this.alpha = value.clamp(0, 255) / 255;
    }


    public get blendMode() {
        return this._graphics.blendMode;
    }
    
    public set blendMode(value: number) {
        this._graphics.blendMode = value;
    }


    /**
     * Sets black to the color of the screen sprite.
     *
     * @method setBlack
     */
    setBlack(): void {
        this.setColor(0, 0, 0);
    };
  
    /**
     * Sets white to the color of the screen sprite.
     *
     * @method setWhite
     */
    setWhite(): void {
        this.setColor(255, 255, 255);
    };
  
    /**
     * Sets the color of the screen sprite by values.
     *
     * @method setColor
     * @param {Number} r The red value in the range (0, 255)
     * @param {Number} g The green value in the range (0, 255)
     * @param {Number} b The blue value in the range (0, 255)
     */
    setColor(r?: number, g?: number, b?: number): void {
        if (this._red !== r || this._green !== g || this._blue !== b) {
            r = Math.round(r || 0).clamp(0, 255);
            g = Math.round(g || 0).clamp(0, 255);
            b = Math.round(b || 0).clamp(0, 255);
            this._red = r;
            this._green = g;
            this._blue = b;
            this._colorText = Utils.rgbToCssColor(r, g, b);
    
            var graphics = this._graphics;
            graphics.clear();
            var intColor = (r << 16) | (g << 8) | b;
            graphics.beginFill(intColor, 1);
            //whole screen with zoom. BWAHAHAHAHA
            graphics.drawRect(-Graphics.width * 5, -Graphics.height * 5, Graphics.width * 10, Graphics.height * 10);
        }
    };

    _graphics: PIXI.Graphics;
    _red: number;
    _green: number;
    _blue: number;
    _colorText: string;
}

//ScreenSprite.prototype = Object.create(PIXI.Container.prototype);
//ScreenSprite.prototype.constructor = ScreenSprite;

//-----------------------------------------------------------------------------
/**
 * The window in the game.
 *
 * @class Window
 * @constructor
 */
class Window extends PIXI.Container {
    constructor(){
        super();
        this.initialize.apply(this, arguments);
    }

    initialize(): void{
        PIXI.Container.call(this);

        this._isWindow = true;
        this._windowskin = null;
        this._width = 0;
        this._height = 0;
        this._cursorRect = new Rectangle();
        this._openness = 255;
        this._animationCount = 0;

        this._padding = 18;
        this._margin = 4;
        this._colorTone = [0, 0, 0];

        this._windowSpriteContainer = null;
        this._windowBackSprite = null;
        this._windowCursorSprite = null;
        this._windowFrameSprite = null;
        this._windowContentsSprite = null;
        this._windowArrowSprites = [];
        this._windowPauseSignSprite = null;

        this._createAllParts();

        /**
         * The origin point of the window for scrolling.
         *
         * @property origin
         * @type Point
         */
        this.origin = new Point();

        /**
         * The active state for the window.
         *
         * @property active
         * @type Boolean
         */
        this.active = true;

        /**
         * The visibility of the down scroll arrow.
         *
         * @property downArrowVisible
         * @type Boolean
         */
        this.downArrowVisible = false;

        /**
         * The visibility of the up scroll arrow.
         *
         * @property upArrowVisible
         * @type Boolean
         */
        this.upArrowVisible = false;

        /**
         * The visibility of the pause sign.
         *
         * @property pause
         * @type Boolean
         */
        this.pause = false;
    };

    /**
     * The image used as a window skin.
     *
     * @property windowskin
     * @type Bitmap
     */
    public get windowskin() {
        return this._windowskin;
    }
    
    public set windowskin(value: Bitmap) {
        if (this._windowskin !== value) {
            this._windowskin = value;
            this._windowskin.addLoadListener(this._onWindowskinLoad.bind(this));
        }
    }


    /**
     * The bitmap used for the window contents.
     *
     * @property contents
     * @type Bitmap
     */
    public get contents() {
        return this._windowContentsSprite.bitmap;
    }
    
    public set contents(value: Bitmap) {
        this._windowContentsSprite.bitmap = value;
    }


    /**
     * The width of the window in pixels.
     *
     * @property width
     * @type Number
     */
    public get width() {
        return this._width;
    }
    
    public set width(value: number) {
        this._width = value;
        this._refreshAllParts();
    }

    /**
     * The height of the window in pixels.
     *
     * @property height
     * @type Number
     */
    public get height() {
        return this._height;
    }
    
    public set height(value: number) {
        this._height = value;
        this._refreshAllParts();
    }


    /**
     * The size of the padding between the frame and contents.
     *
     * @property padding
     * @type Number
     */
    public get padding() {
        return this._padding;
    }
    
    public set padding(value: number) {
        this._padding = value;
        this._refreshAllParts();
    }

    /**
     * The size of the margin for the window background.
     *
     * @property margin
     * @type Number
     */
    public get margin() {
        return this._margin;
    }
    
    public set margin(value: number) {
        this._margin = value;
        this._refreshAllParts();
    }

    /**
     * The opacity of the window without contents (0 to 255).
     *
     * @property opacity
     * @type Number
     */
    public get opacity() {
        return this._windowSpriteContainer.alpha * 255;
    }
    
    public set opacity(value: number) {
        this._windowSpriteContainer.alpha = value.clamp(0, 255) / 255;
    }

    /**
     * The opacity of the window background (0 to 255).
     *
     * @property backOpacity
     * @type Number
     */
    public get backOpacity() {
        return this._windowBackSprite.alpha * 255;
    }
    
    public set backOpacity(value: number) {
        this._windowBackSprite.alpha = value.clamp(0, 255) / 255;
    }

    /**
     * The opacity of the window contents (0 to 255).
     *
     * @property contentsOpacity
     * @type Number
     */
    public get contentsOpacity() {
        return this._windowContentsSprite.alpha * 255;
    }
    
    public set contentsOpacity(value: number) {
        this._windowContentsSprite.alpha = value.clamp(0, 255) / 255;
    }

    /**
     * The openness of the window (0 to 255).
     *
     * @property openness
     * @type Number
     */
    public get openness() {
        return this._openness;
    }
    
    public set openness(value: number) {
        if (this._openness !== value) {
            this._openness = value.clamp(0, 255);
            this._windowSpriteContainer.scale.y = this._openness / 255;
            this._windowSpriteContainer.y = this.height / 2 * (1 - this._openness / 255);
        }
    }

    /**
     * Updates the window for each frame.
     *
     * @method update
     */
    update(): void {
        if (this.active) {
            this._animationCount++;
        }
        this.children.forEach(function(child) {
            if (child.update) {
                child.update();
            }
        });
    };

    /**
     * Sets the x, y, width, and height all at once.
     *
     * @method move
     * @param {Number} x The x coordinate of the window
     * @param {Number} y The y coordinate of the window
     * @param {Number} width The width of the window
     * @param {Number} height The height of the window
     */
    move(x?: number, y?: number, width?: number, height?: number): void {
        this.x = x || 0;
        this.y = y || 0;
        if (this._width !== width || this._height !== height) {
            this._width = width || 0;
            this._height = height || 0;
            this._refreshAllParts();
        }
    };

    /**
     * Returns true if the window is completely open (openness == 255).
     *
     * @method isOpen
     */   
    isOpen(): boolean {
        return this._openness >= 255;
    };

    /**
     * Returns true if the window is completely closed (openness == 0).
     *
     * @method isClosed
     */   
    isClosed(): boolean {
        return this._openness <= 0;
    };

    /**
     * Sets the position of the command cursor.
     *
     * @method setCursorRect
     * @param {Number} x The x coordinate of the cursor
     * @param {Number} y The y coordinate of the cursor
     * @param {Number} width The width of the cursor
     * @param {Number} height The height of the cursor
     */    
    setCursorRect(x?: number, y?: number, width?: number, height?: number): void {
        var cx = Math.floor(x || 0);
        var cy = Math.floor(y || 0);
        var cw = Math.floor(width || 0);
        var ch = Math.floor(height || 0);
        var rect = this._cursorRect;
        if (rect.x !== cx || rect.y !== cy || rect.width !== cw || rect.height !== ch) {
            this._cursorRect.x = cx;
            this._cursorRect.y = cy;
            this._cursorRect.width = cw;
            this._cursorRect.height = ch;
            this._refreshCursor();
        }
    };

    /**
     * Changes the color of the background.
     *
     * @method setTone
     * @param {Number} r The red value in the range (-255, 255)
     * @param {Number} g The green value in the range (-255, 255)
     * @param {Number} b The blue value in the range (-255, 255)
     */   
    setTone(r: number, g: number, b: number): void {
        var tone = this._colorTone;
        if (r !== tone[0] || g !== tone[1] || b !== tone[2]) {
            this._colorTone = [r, g, b];
            this._refreshBack();
        }
    };

    /**
     * Adds a child between the background and contents.
     *
     * @method addChildToBack
     * @param {Object} child The child to add
     * @return {Object} The child that was added
     */   
    addChildToBack(child: PIXI.DisplayObject): PIXI.DisplayObject {
        var containerIndex = this.children.indexOf(this._windowSpriteContainer);
        return this.addChildAt(child, containerIndex + 1);
    };

    /**
     * @method updateTransform
     * @private
     */  
    updateTransform(): void {
        this._updateCursor();
        this._updateArrows();
        this._updatePauseSign();
        this._updateContents();
        PIXI.Container.prototype.updateTransform.call(this);
    };

    /**
     * @method _createAllParts
     * @private
     */
    _createAllParts(): void {
        this._windowSpriteContainer = new PIXI.Container();
        this._windowBackSprite = new Sprite();
        this._windowCursorSprite = new Sprite();
        this._windowFrameSprite = new Sprite();
        this._windowContentsSprite = new Sprite();
        this._downArrowSprite = new Sprite();
        this._upArrowSprite = new Sprite();
        this._windowPauseSignSprite = new Sprite();
        this._windowBackSprite.bitmap = new Bitmap(1, 1);
        this._windowBackSprite.alpha = 192 / 255;
        this.addChild(this._windowSpriteContainer);
        this._windowSpriteContainer.addChild(this._windowBackSprite);
        this._windowSpriteContainer.addChild(this._windowFrameSprite);
        this.addChild(this._windowCursorSprite);
        this.addChild(this._windowContentsSprite);
        this.addChild(this._downArrowSprite);
        this.addChild(this._upArrowSprite);
        this.addChild(this._windowPauseSignSprite);
    };

    /**
     * @method _onWindowskinLoad
     * @private
     */
    _onWindowskinLoad(): void {
        this._refreshAllParts();
    };

    /**
     * @method _refreshAllParts
     * @private
     */   
    _refreshAllParts(): void {
        this._refreshBack();
        this._refreshFrame();
        this._refreshCursor();
        this._refreshContents();
        this._refreshArrows();
        this._refreshPauseSign();
    };

    /**
     * @method _refreshBack
     * @private
     */   
    _refreshBack(): void {
        var m = this._margin;
        var w = this._width - m * 2;
        var h = this._height - m * 2;
        var bitmap = new Bitmap(w, h);
    
        this._windowBackSprite.bitmap = bitmap;
        this._windowBackSprite.setFrame(0, 0, w, h);
        this._windowBackSprite.move(m, m);
    
        if (w > 0 && h > 0 && this._windowskin) {
            var p = 96;
            bitmap.blt(this._windowskin, 0, 0, p, p, 0, 0, w, h);
            for (var y = 0; y < h; y += p) {
                for (var x = 0; x < w; x += p) {
                    bitmap.blt(this._windowskin, 0, p, p, p, x, y, p, p);
                }
            }
            var tone = this._colorTone;
            bitmap.adjustTone(tone[0], tone[1], tone[2]);
        }
    };

   
    /**
     * @method _refreshFrame
     * @private
     */
    _refreshFrame(): void {
        var w = this._width;
        var h = this._height;
        var m = 24;
        var bitmap = new Bitmap(w, h);
    
        this._windowFrameSprite.bitmap = bitmap;
        this._windowFrameSprite.setFrame(0, 0, w, h);
    
        if (w > 0 && h > 0 && this._windowskin) {
            var skin = this._windowskin;
            var p = 96;
            var q = 96;
            bitmap.blt(skin, p+m, 0+0, p-m*2, m, m, 0, w-m*2, m);
            bitmap.blt(skin, p+m, 0+q-m, p-m*2, m, m, h-m, w-m*2, m);
            bitmap.blt(skin, p+0, 0+m, m, p-m*2, 0, m, m, h-m*2);
            bitmap.blt(skin, p+q-m, 0+m, m, p-m*2, w-m, m, m, h-m*2);
            bitmap.blt(skin, p+0, 0+0, m, m, 0, 0, m, m);
            bitmap.blt(skin, p+q-m, 0+0, m, m, w-m, 0, m, m);
            bitmap.blt(skin, p+0, 0+q-m, m, m, 0, h-m, m, m);
            bitmap.blt(skin, p+q-m, 0+q-m, m, m, w-m, h-m, m, m);
        }
    };

    /**
     * @method _refreshCursor
     * @private
     */
    _refreshCursor(): void {
        var pad = this._padding;
        var x = this._cursorRect.x + pad - this.origin.x;
        var y = this._cursorRect.y + pad - this.origin.y;
        var w = this._cursorRect.width;
        var h = this._cursorRect.height;
        var m = 4;
        var x2 = Math.max(x, pad);
        var y2 = Math.max(y, pad);
        var ox = x - x2;
        var oy = y - y2;
        var w2 = Math.min(w, this._width - pad - x2);
        var h2 = Math.min(h, this._height - pad - y2);
        var bitmap = new Bitmap(w2, h2);
    
        this._windowCursorSprite.bitmap = bitmap;
        this._windowCursorSprite.setFrame(0, 0, w2, h2);
        this._windowCursorSprite.move(x2, y2);
    
        if (w > 0 && h > 0 && this._windowskin) {
            var skin = this._windowskin;
            var p = 96;
            var q = 48;
            bitmap.blt(skin, p+m, p+m, q-m*2, q-m*2, ox+m, oy+m, w-m*2, h-m*2);
            bitmap.blt(skin, p+m, p+0, q-m*2, m, ox+m, oy+0, w-m*2, m);
            bitmap.blt(skin, p+m, p+q-m, q-m*2, m, ox+m, oy+h-m, w-m*2, m);
            bitmap.blt(skin, p+0, p+m, m, q-m*2, ox+0, oy+m, m, h-m*2);
            bitmap.blt(skin, p+q-m, p+m, m, q-m*2, ox+w-m, oy+m, m, h-m*2);
            bitmap.blt(skin, p+0, p+0, m, m, ox+0, oy+0, m, m);
            bitmap.blt(skin, p+q-m, p+0, m, m, ox+w-m, oy+0, m, m);
            bitmap.blt(skin, p+0, p+q-m, m, m, ox+0, oy+h-m, m, m);
            bitmap.blt(skin, p+q-m, p+q-m, m, m, ox+w-m, oy+h-m, m, m);
        }
    };

    /**
     * @method _refreshContents
     * @private
     */
    _refreshContents(): void {
        this._windowContentsSprite.move(this.padding, this.padding);
    };

    /**
     * @method _refreshArrows
     * @private
     */
    _refreshArrows(): void {
        var w = this._width;
        var h = this._height;
        var p = 24;
        var q = p/2;
        var sx = 96+p;
        var sy = 0+p;
        this._downArrowSprite.bitmap = this._windowskin;
        this._downArrowSprite.anchor.x = 0.5;
        this._downArrowSprite.anchor.y = 0.5;
        this._downArrowSprite.setFrame(sx+q, sy+q+p, p, q);
        this._downArrowSprite.move(w/2, h-q);
        this._upArrowSprite.bitmap = this._windowskin;
        this._upArrowSprite.anchor.x = 0.5;
        this._upArrowSprite.anchor.y = 0.5;
        this._upArrowSprite.setFrame(sx+q, sy, p, q);
        this._upArrowSprite.move(w/2, q);
    };

    /**
     * @method _refreshPauseSign
     * @private
     */
    _refreshPauseSign(): void {
        var sx = 144;
        var sy = 96;
        var p = 24;
        this._windowPauseSignSprite.bitmap = this._windowskin;
        this._windowPauseSignSprite.anchor.x = 0.5;
        this._windowPauseSignSprite.anchor.y = 1;
        this._windowPauseSignSprite.move(this._width / 2, this._height);
        this._windowPauseSignSprite.setFrame(sx, sy, p, p);
        this._windowPauseSignSprite.alpha = 0;
    };

    /**
     * @method _updateCursor
     * @private
     */
    _updateCursor(): void {
        var blinkCount = this._animationCount % 40;
        var cursorOpacity = this.contentsOpacity;
        if (this.active) {
            if (blinkCount < 20) {
                cursorOpacity -= blinkCount * 8;
            } else {
                cursorOpacity -= (40 - blinkCount) * 8;
            }
        }
        this._windowCursorSprite.alpha = cursorOpacity / 255;
        this._windowCursorSprite.visible = this.isOpen();
    };

    /**
     * @method _updateContents
     * @private
     */
    _updateContents(): void {
        var w = this._width - this._padding * 2;
        var h = this._height - this._padding * 2;
        if (w > 0 && h > 0) {
            this._windowContentsSprite.setFrame(this.origin.x, this.origin.y, w, h);
            this._windowContentsSprite.visible = this.isOpen();
        } else {
            this._windowContentsSprite.visible = false;
        }
    };

    /**
     * @method _updateArrows
     * @private
     */
    _updateArrows(): void {
        this._downArrowSprite.visible = this.isOpen() && this.downArrowVisible;
        this._upArrowSprite.visible = this.isOpen() && this.upArrowVisible;
    };

    /**
     * @method _updatePauseSign
     * @private
     */
    _updatePauseSign(): void {
        var sprite = this._windowPauseSignSprite;
        var x = Math.floor(this._animationCount / 16) % 2;
        var y = Math.floor(this._animationCount / 16 / 2) % 2;
        var sx = 144;
        var sy = 96;
        var p = 24;
        if (!this.pause) {
            sprite.alpha = 0;
        } else if (sprite.alpha < 1) {
            sprite.alpha = Math.min(sprite.alpha + 0.1, 1);
        }
        sprite.setFrame(sx+x*p, sy+y*p, p, p);
        sprite.visible = this.isOpen();
    };


    _downArrowSprite: Sprite;
    _upArrowSprite: Sprite;

    origin: Point;
    active: boolean;
    downArrowVisible: boolean;
    upArrowVisible: boolean;
    pause: boolean;

    _isWindow: boolean;
    _windowskin: Bitmap;
    _width: number;
    _height: number;
    _cursorRect: Rectangle;
    _openness: number;
    _animationCount: number;
    _padding: number;
    _margin: number;
    _colorTone: number[];
    _windowSpriteContainer: PIXI.Container;
    _windowBackSprite: Sprite;
    _windowCursorSprite: Sprite;
    _windowFrameSprite: Sprite;
    _windowContentsSprite: Sprite;
    _windowArrowSprites: Sprite[];
    _windowPauseSignSprite: Sprite;
}






//=============================



// The important members from Pixi.js

/**
 * The visibility of the window.
 *
 * @property visible
 * @type Boolean
 */

/**
 * The x coordinate of the window.
 *
 * @property x
 * @type Number
 */

/**
 * The y coordinate of the window.
 *
 * @property y
 * @type Number
 */

/**
 * [read-only] The array of children of the window.
 *
 * @property children
 * @type Array
 */

/**
 * [read-only] The object that contains the window.
 *
 * @property parent
 * @type Object
 */

/**
 * Adds a child to the container.
 *
 * @method addChild
 * @param {Object} child The child to add
 * @return {Object} The child that was added
 */

/**
 * Adds a child to the container at a specified index.
 *
 * @method addChildAt
 * @param {Object} child The child to add
 * @param {Number} index The index to place the child in
 * @return {Object} The child that was added
 */

/**
 * Removes a child from the container.
 *
 * @method removeChild
 * @param {Object} child The child to remove
 * @return {Object} The child that was removed
 */

/**
 * Removes a child from the specified index position.
 *
 * @method removeChildAt
 * @param {Number} index The index to get the child from
 * @return {Object} The child that was removed
 */

//-----------------------------------------------------------------------------
/**
 * The layer which contains game windows.
 *
 * @class WindowLayer
 * @constructor
 */
class WindowLayer extends PIXI.Container {
    constructor() {
        super();
        this.initialize.apply(this, arguments);
    }

    initialize(): void {
        PIXI.Container.call(this);
        this._width = 0;
        this._height = 0;
        this._tempCanvas = null;
        this._translationMatrix = [1, 0, 0, 0, 1, 0, 0, 0, 1];
    
        this._windowMask = new PIXI.Graphics();
        this._windowMask.beginFill(0xffffff, 1);
        this._windowMask.drawRect(0, 0, 0, 0);
        this._windowMask.endFill();
        this._windowRect = this._windowMask.graphicsData[0].shape;
    
        this._renderSprite = null;
        this.filterArea = new PIXI.Rectangle();
        this.filters = [WindowLayer.voidFilter];
    
        //temporary fix for memory leak bug
        this.on('removed', this.onRemoveAsAChild);
    };

    onRemoveAsAChild(): void {
        this.removeChildren();
    }

    static voidFilter: PIXI.filters.VoidFilter =  new PIXI.filters.VoidFilter();

    /**
     * The width of the window layer in pixels.
     *
     * @property width
     * @type Number
     */
    public get width(){
        return this._width;
    };

    public set width(value: number){
        this._width = value;
    };

    /**
     * The height of the window layer in pixels.
     *
     * @property height
     * @type Number
     */
    public get height(){
        return this._height;
    }
    public set height(value: number){
        this._height = value;
    }

    /**
     * Sets the x, y, width, and height all at once.
     *
     * @method move
     * @param {Number} x The x coordinate of the window layer
     * @param {Number} y The y coordinate of the window layer
     * @param {Number} width The width of the window layer
     * @param {Number} height The height of the window layer
     */
    move(x: number, y: number, width: number, height: number): void {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    };
 
    /**
     * Updates the window layer for each frame.
     *
     * @method update
     */
    update(): void {
        this.children.forEach(function(child) {
            if (child.update) {
                child.update();
            }
        });
    };

    /**
     * @method _renderCanvas
     * @param {Object} renderSession
     * @private
     */
    renderCanvas(renderer: PIXI.CanvasRenderer): void {
        if (!this.visible || !this.renderable) {
            return;
        }
    
        if (!this._tempCanvas) {
            this._tempCanvas = document.createElement('canvas');
        }
    
        this._tempCanvas.width = Graphics.width;
        this._tempCanvas.height = Graphics.height;
    
        var realCanvasContext = renderer.context;
        var context = this._tempCanvas.getContext('2d');
    
        context.save();
        context.clearRect(0, 0, Graphics.width, Graphics.height);
        context.beginPath();
        context.rect(this.x, this.y, this.width, this.height);
        context.closePath();
        context.clip();
    
        renderer.context = context;
    
        for (var i = 0; i < this.children.length; i++) {
            var child = this.children[i];
            if (child._isWindow && child.visible && child.openness > 0) {
                this._canvasClearWindowRect(renderer, child);
                context.save();
                child.renderCanvas(renderer);
                context.restore();
            }
        }
    
        context.restore();
    
        renderer.context = realCanvasContext;
        renderer.context.setTransform(1, 0, 0, 1, 0, 0);
        renderer.context.globalCompositeOperation = 'source-over';
        renderer.context.globalAlpha = 1;
        renderer.context.drawImage(this._tempCanvas, 0, 0);
    
        for (var j = 0; j < this.children.length; j++) {
            if (!this.children[j]._isWindow) {
                this.children[j].renderCanvas(renderer);
            }
        }
    };

    /**
     * @method _canvasClearWindowRect
     * @param {Object} renderSession
     * @param {Window} window
     * @private
     */
    _canvasClearWindowRect(renderSession: PIXI.CanvasRenderer, window: Window|any): void {
        var rx = this.x + window.x;
        var ry = this.y + window.y + window.height / 2 * (1 - window._openness / 255);
        var rw = window.width;
        var rh = window.height * window._openness / 255;
        renderSession.context.clearRect(rx, ry, rw, rh);
    };
   
   /**
     * @method _renderWebGL
     * @param {Object} renderSession
     * @private
     */
    renderWebGL(renderer: PIXI.WebGLRenderer): void {
        if (!this.visible || !this.renderable) {
            return;
        }
    
        if (this.children.length==0) {
            return;
        }
    
        renderer.flush();
        this.filterArea.copy(this);
        renderer.filterManager.pushFilter(this, this.filters);
        renderer.currentRenderer.start();
    
        var shift = new PIXI.Point();
        var rt = renderer._activeRenderTarget;
        var projectionMatrix = rt.projectionMatrix;
        shift.x = Math.round((projectionMatrix.tx + 1) / 2 * rt.sourceFrame.width);
        shift.y = Math.round((projectionMatrix.ty + 1) / 2 * rt.sourceFrame.height);
    
        for (var i = 0; i < this.children.length; i++) {
            var child = this.children[i];
            if (child._isWindow && child.visible && child.openness > 0) {
                this._maskWindow(child, shift);
                renderer.maskManager.pushScissorMask(this, this._windowMask);
                renderer.clear();
                renderer.maskManager.popScissorMask();
                renderer.currentRenderer.start();
                child.renderWebGL(renderer);
                renderer.currentRenderer.flush();
            }
        }
    
        renderer.flush();
        renderer.filterManager.popFilter();
        renderer.maskManager.popScissorMask();
    
        for (var j = 0; j < this.children.length; j++) {
            if (!this.children[j]._isWindow) {
                this.children[j].renderWebGL(renderer);
            }
        }
    };

    /**
     * @method _maskWindow
     * @param {Window} window
     * @private
     */
    _maskWindow(window: Window|any, shift: PIXI.Point): void {
        this._windowMask._currentBounds = null;
        this._windowMask.boundsDirty = true;
        var rect = this._windowRect;
        rect.x = this.x + shift.x + window.x;
        rect.y = this.x + shift.y + window.y + window.height / 2 * (1 - window._openness / 255);
        rect.width = window.width;
        rect.height = window.height * window._openness / 255;
    };

    //_isWindow:any;
    filterArea: PIXI.Rectangle;
    _width: number;
    _height: number;
    _tempCanvas: HTMLCanvasElement;
    _translationMatrix: number[];
    _windowMask: any;
    _windowRect: PIXI.Rectangle;
    _renderSprite: Sprite;
}
//WindowLayer.prototype = Object.create(PIXI.Container.prototype);
//WindowLayer.prototype.constructor = WindowLayer;


// The important members from Pixi.js

/**
 * The x coordinate of the window layer.
 *
 * @property x
 * @type Number
 */

/**
 * The y coordinate of the window layer.
 *
 * @property y
 * @type Number
 */

/**
 * [read-only] The array of children of the window layer.
 *
 * @property children
 * @type Array
 */

/**
 * [read-only] The object that contains the window layer.
 *
 * @property parent
 * @type Object
 */

/**
 * Adds a child to the container.
 *
 * @method addChild
 * @param {Object} child The child to add
 * @return {Object} The child that was added
 */

/**
 * Adds a child to the container at a specified index.
 *
 * @method addChildAt
 * @param {Object} child The child to add
 * @param {Number} index The index to place the child in
 * @return {Object} The child that was added
 */

/**
 * Removes a child from the container.
 *
 * @method removeChild
 * @param {Object} child The child to remove
 * @return {Object} The child that was removed
 */

/**
 * Removes a child from the specified index position.
 *
 * @method removeChildAt
 * @param {Number} index The index to get the child from
 * @return {Object} The child that was removed
 */

//-----------------------------------------------------------------------------
/**
 * The weather effect which displays rain, storm, or snow.
 *
 * @class Weather
 * @constructor
 */
class Weather extends PIXI.Container {
    viewport: Bitmap;
    constructor(){
        super();
        this.initialize.apply(this, arguments);
    };


    initialize(): void{
        PIXI.Container.call(this);

        this._width = Graphics.width;
        this._height = Graphics.height;
        this._sprites = [];
    
        this._createBitmaps();
        this._createDimmer();
    
        /**
         * The type of the weather in ['none', 'rain', 'storm', 'snow'].
         *
         * @property type
         * @type String
         */
        this.type = 'none';
    
        /**
         * The power of the weather in the range (0, 9).
         *
         * @property power
         * @type Number
         */
        this.power = 0;
    
        /**
         * The origin point of the weather for scrolling.
         *
         * @property origin
         * @type Point
         */
        this.origin = new Point();
    };

    /**
     * Updates the weather for each frame.
     *
     * @method update
     */
    update(): void{ 
        this._updateDimmer();
        this._updateAllSprites();
    };


    /**
     * @method _createBitmaps
     * @private
     */
    _createBitmaps(): void {
        this._rainBitmap = new Bitmap(1, 60);
        this._rainBitmap.fillAll('white');
        this._stormBitmap = new Bitmap(2, 100);
        this._stormBitmap.fillAll('white');
        this._snowBitmap = new Bitmap(9, 9);
        this._snowBitmap.drawCircle(4, 4, 4, 'white');
    };

    /**
     * @method _createDimmer
     * @private
     */
    _createDimmer(): void {
        this._dimmerSprite = new ScreenSprite();
        this._dimmerSprite.setColor(80, 80, 80);
        this.addChild(this._dimmerSprite);
    };

    /**
     * @method _updateDimmer
     * @private
     */
    _updateDimmer(): void {
        this._dimmerSprite.opacity = Math.floor(this.power * 6);
    };

    /**
     * @method _updateAllSprites
     * @private
     */
    _updateAllSprites(): void {
        var maxSprites = Math.floor(this.power * 10);
        while (this._sprites.length < maxSprites) {
            this._addSprite();
        }
        while (this._sprites.length > maxSprites) {
            this._removeSprite();
        }
        this._sprites.forEach((sprite) => {
            this._updateSprite(sprite);
            sprite.x = sprite.ax - this.origin.x;
            sprite.y = sprite.ay - this.origin.y;
        }, this);
    };

    /**
     * @method _addSprite
     * @private
     */
    _addSprite(): void {
        var sprite = new Sprite(this.viewport);
        sprite.opacity = 0;
        this._sprites.push(sprite);
        this.addChild(sprite);
    };

    /**
     * @method _removeSprite
     * @private
     */
    _removeSprite(): void {
        this.removeChild(this._sprites.pop());
    };

    /**
     * @method _updateSprite
     * @param {Sprite} sprite
     * @private
     */
    _updateSprite(sprite: Sprite): void {
        switch (this.type) {
        case 'rain':
            this._updateRainSprite(sprite);
            break;
        case 'storm':
            this._updateStormSprite(sprite);
            break;
        case 'snow':
            this._updateSnowSprite(sprite);
            break;
        }
        if (sprite.opacity < 40) {
            this._rebornSprite(sprite);
        }
    };

    /**
     * @method _updateRainSprite
     * @param {Sprite} sprite
     * @private
     */
    _updateRainSprite(sprite: Sprite): void {
        sprite.bitmap = this._rainBitmap;
        sprite.rotation = Math.PI / 16;
        sprite.ax -= 6 * Math.sin(sprite.rotation);
        sprite.ay += 6 * Math.cos(sprite.rotation);
        sprite.opacity -= 6;
    };

    /**
     * @method _updateStormSprite
     * @param {Sprite} sprite
     * @private
     */
    _updateStormSprite(sprite: Sprite): void {
        sprite.bitmap = this._stormBitmap;
        sprite.rotation = Math.PI / 8;
        sprite.ax -= 8 * Math.sin(sprite.rotation);
        sprite.ay += 8 * Math.cos(sprite.rotation);
        sprite.opacity -= 8;
    };

    /**
     * @method _updateSnowSprite
     * @param {Sprite} sprite
     * @private
     */
    _updateSnowSprite(sprite: Sprite): void {
        sprite.bitmap = this._snowBitmap;
        sprite.rotation = Math.PI / 16;
        sprite.ax -= 3 * Math.sin(sprite.rotation);
        sprite.ay += 3 * Math.cos(sprite.rotation);
        sprite.opacity -= 3;
    };

    /**
     * @method _rebornSprite
     * @param {Sprite} sprite
     * @private
     */
    _rebornSprite(sprite: Sprite): void {
        sprite.ax = Math.randomInt(Graphics.width + 100) - 100 + this.origin.x;
        sprite.ay = Math.randomInt(Graphics.height + 200) - 200 + this.origin.y;
        sprite.opacity = 160 + Math.randomInt(60);
    };


    _width: number;
    _height: number;
    _sprites: Sprite[];
    _rainBitmap: Bitmap;
    _stormBitmap: Bitmap;
    _snowBitmap: Bitmap;
    _dimmerSprite: ScreenSprite;

    type: string;
    power: number;
    origin: Point;
}
//Weather.prototype = Object.create(PIXI.Container.prototype);
//Weather.prototype.constructor = Weather;



//-----------------------------------------------------------------------------
/**
 * The color matrix filter for WebGL.
 *
 * @class ToneFilter
 * @extends PIXI.Filter
 * @constructor
 */
class ToneFilter extends PIXI.filters.ColorMatrixFilter {
    constructor(){
        super();
        PIXI.filters.ColorMatrixFilter.call(this);
    }
    /**
     * Changes the hue.
     *
     * @method adjustHue
     * @param {Number} value The hue value in the range (-360, 360)
     */
    adjustHue(value: number): void {
        this.hue(value, true);
    };

    /**
     * Changes the saturation.
     *
     * @method adjustSaturation
     * @param {Number} value The saturation value in the range (-255, 255)
     */
    adjustSaturation(value?: number): void {
        value = (value || 0).clamp(-255, 255) / 255;
        this.saturate(value, true);
    };

    /**
     * Changes the tone.
     *
     * @method adjustTone
     * @param {Number} r The red strength in the range (-255, 255)
     * @param {Number} g The green strength in the range (-255, 255)
     * @param {Number} b The blue strength in the range (-255, 255)
     */
    adjustTone(r?: number, g?: number, b?: number): void {
        r = (r || 0).clamp(-255, 255) / 255;
        g = (g || 0).clamp(-255, 255) / 255;
        b = (b || 0).clamp(-255, 255) / 255;
    
        if (r !== 0 || g !== 0 || b !== 0) {
            var matrix = [
                1, 0, 0, r, 0,
                0, 1, 0, g, 0,
                0, 0, 1, b, 0,
                0, 0, 0, 1, 0
            ];
    
            this._loadMatrix(matrix, true);
        }
    };
}
//ToneFilter.prototype = Object.create(PIXI.filters.ColorMatrixFilter.prototype);
//ToneFilter.prototype.constructor = ToneFilter;

//-----------------------------------------------------------------------------
/**
 * The sprite which changes the screen color in 2D canvas mode.
 *
 * @class ToneSprite
 * @constructor
 */
class ToneSprite extends PIXI.Container {
    constructor(){
        super();
    };
    initialize(): void{
        PIXI.Container.call(this);
        this.clear();
    };

    /**
     * Clears the tone.
     *
     * @method reset
     */
    clear(): void {
        this._red = 0;
        this._green = 0;
        this._blue = 0;
        this._gray = 0;
    };

    /**
     * Sets the tone.
     *
     * @method setTone
     * @param {Number} r The red strength in the range (-255, 255)
     * @param {Number} g The green strength in the range (-255, 255)
     * @param {Number} b The blue strength in the range (-255, 255)
     * @param {Number} gray The grayscale level in the range (0, 255)
     */
    setTone(r?: number, g?: number, b?: number, gray?: number): void {
        this._red = Math.round(r || 0).clamp(-255, 255);
        this._green = Math.round(g || 0).clamp(-255, 255);
        this._blue = Math.round(b || 0).clamp(-255, 255);
        this._gray = Math.round(gray || 0).clamp(0, 255);
    };

    /**
     * @method _renderCanvas
     * @param {Object} renderSession
     * @private
     */
    _renderCanvas(renderer: PIXI.CanvasRenderer): void {
        if (this.visible) {
            var context = renderer.context;
            var t = this.worldTransform;
            var r = renderer.resolution;
            var width = Graphics.width;
            var height = Graphics.height;
            context.save();
            context.setTransform(t.a, t.b, t.c, t.d, t.tx * r, t.ty * r);
            if (Graphics.canUseSaturationBlend() && this._gray > 0) {
                context.globalCompositeOperation = 'saturation';
                context.globalAlpha = this._gray / 255;
                context.fillStyle = '#ffffff';
                context.fillRect(0, 0, width, height);
            }
            context.globalAlpha = 1;
            var r1 = Math.max(0, this._red);
            var g1 = Math.max(0, this._green);
            var b1 = Math.max(0, this._blue);
            if (r1 || g1 || b1) {
                context.globalCompositeOperation = 'lighter';
                context.fillStyle = Utils.rgbToCssColor(r1, g1, b1);
                context.fillRect(0, 0, width, height);
            }
            if (Graphics.canUseDifferenceBlend()) {
                var r2 = Math.max(0, -this._red);
                var g2 = Math.max(0, -this._green);
                var b2 = Math.max(0, -this._blue);
                if (r2 || g2 || b2) {
                    context.globalCompositeOperation = 'difference';
                    context.fillStyle = '#ffffff';
                    context.fillRect(0, 0, width, height);
                    context.globalCompositeOperation = 'lighter';
                    context.fillStyle = Utils.rgbToCssColor(r2, g2, b2);
                    context.fillRect(0, 0, width, height);
                    context.globalCompositeOperation = 'difference';
                    context.fillStyle = '#ffffff';
                    context.fillRect(0, 0, width, height);
                }
            }
            context.restore();
        }
    };

    /**
     * @method _renderWebGL
     * @param {Object} renderSession
     * @private
     */
    _renderWebGL(renderer: PIXI.WebGLRenderer): void {
        // Not supported
    };

    _red: number;
    _green: number;
    _blue: number;
    _gray: number;
}
//ToneSprite.prototype = Object.create(PIXI.Container.prototype);
//ToneSprite.prototype.constructor = ToneSprite;

//-----------------------------------------------------------------------------
/**
 * The root object of the display tree.
 *
 * @class Stage
 * @constructor
 */
class Stage extends PIXI.Container {
    constructor(){
        super();
        this.initialize.apply(this, arguments);
    };
    initialize(): void {
        PIXI.Container.call(this);
    
        // The interactive flag causes a memory leak.
        this.interactive = false;
    };
}

/**
 * [read-only] The array of children of the stage.
 *
 * @property children
 * @type Array
 */

/**
 * Adds a child to the container.
 *
 * @method addChild
 * @param {Object} child The child to add
 * @return {Object} The child that was added
 */

/**
 * Adds a child to the container at a specified index.
 *
 * @method addChildAt
 * @param {Object} child The child to add
 * @param {Number} index The index to place the child in
 * @return {Object} The child that was added
 */

/**
 * Removes a child from the container.
 *
 * @method removeChild
 * @param {Object} child The child to remove
 * @return {Object} The child that was removed
 */

/**
 * Removes a child from the specified index position.
 *
 * @method removeChildAt
 * @param {Number} index The index to get the child from
 * @return {Object} The child that was removed
 */

//-----------------------------------------------------------------------------
/**
 * The audio object of Web Audio API.
 *
 * @class WebAudio
 * @constructor
 * @param {String} url The url of the audio file
 */
var webkitAudioContext: new () => AudioContext;
class WebAudio {
    
    constructor(url: string){
        this.initialize.apply(this, arguments);
    }
   
    static _standAlone: any;

    initialize(url: string): void {
        if (!WebAudio._initialized) {
            WebAudio.initialize();
        }
        this.clear();
        //static createLoader(url: string, retryMethod: Function, resignMethod: Function, retryInterval: number): void;

        if(!WebAudio._standAlone){
            this._loader = ResourceHandler.createLoader(url, this._load.bind(this, url), function(this:any) {
                this._hasError = true;
            }.bind(this));
        }
        this._load(url);
        this._url = url;
    };

    
    static _masterVolume: number = 1;
    static _context: AudioContext = null;
    static _masterGainNode: GainNode = null;
    static _initialized: boolean = false;
    static _unlocked: boolean = false;

    /**
     * Initializes the audio system.
     *
     * @static
     * @method initialize
     * @param {Boolean} noAudio Flag for the no-audio mode
     * @return {Boolean} True if the audio system is available
     */
    static initialize(noAudio?: boolean|any): boolean {
        if (!this._initialized) {
            if (!noAudio) {
                this._createContext();
                this._detectCodecs();
                this._createMasterGainNode();
                this._setupEventHandlers();
            }
            this._initialized = true;
        }
        return !!this._context;
    };

    /**
     * Checks whether the browser can play ogg files.
     *
     * @static
     * @method canPlayOgg
     * @return {Boolean} True if the browser can play ogg files
     */
    static canPlayOgg(): boolean {
        if (!this._initialized) {
            this.initialize();
        }
        return !!this._canPlayOgg;
    };

    /**
     * Checks whether the browser can play m4a files.
     *
     * @static
     * @method canPlayM4a
     * @return {Boolean} True if the browser can play m4a files
     */
    static canPlayM4a(): boolean {
        if (!this._initialized) {
            this.initialize();
        }
        return !!this._canPlayM4a;
    };

    /**
     * Sets the master volume of the all audio.
     *
     * @static
     * @method setMasterVolume
     * @param {Number} value Master volume (min: 0, max: 1)
     */
    static setMasterVolume(value: number): void {
        this._masterVolume = value;
        if (this._masterGainNode) {
            this._masterGainNode.gain.setValueAtTime(this._masterVolume, this._context.currentTime);
        }
    };

    /**
     * @static
     * @method _createContext
     * @private
     */
    static _createContext(): void {
        try {
            if (typeof AudioContext !== 'undefined') {
                this._context = new AudioContext();
            } else if (typeof webkitAudioContext !== 'undefined') {
                this._context = new webkitAudioContext();
            }
        } catch (e) {
            this._context = null;
        }
    };

    

    /**
     * @static
     * @method _detectCodecs
     * @private
     */
    static _detectCodecs(): void {
        var audio = document.createElement('audio');
        if (audio.canPlayType) {
            this._canPlayOgg = audio.canPlayType('audio/ogg');
            this._canPlayM4a = audio.canPlayType('audio/mp4');
        }
    };

    /**
     * @static
     * @method _createMasterGainNode
     * @private
     */
    static _createMasterGainNode(): void {
        var context = WebAudio._context;
        if (context) {
            this._masterGainNode = context.createGain();
            this._masterGainNode.gain.setValueAtTime(this._masterVolume, context.currentTime);
            this._masterGainNode.connect(context.destination);
        }
    };

    /**
     * @static
     * @method _setupEventHandlers
     * @private
     */
    static _setupEventHandlers(): void {
        var resumeHandler = function() {
            var context = WebAudio._context;
            if (context && context.state === "suspended" && typeof context.resume === "function") {
                context.resume().then(function() {
                    WebAudio._onTouchStart();
                })
            } else {
                WebAudio._onTouchStart();
            }
        };
        document.addEventListener("keydown", resumeHandler);
        document.addEventListener("mousedown", resumeHandler);
        document.addEventListener("touchend", resumeHandler);
        document.addEventListener('touchstart', this._onTouchStart.bind(this));
        document.addEventListener('visibilitychange', this._onVisibilityChange.bind(this));
    };

    /**
     * @static
     * @method _onTouchStart
     * @private
     */
    static _onTouchStart(): void {
        var context = WebAudio._context;
        if (context && !this._unlocked) {
            // Unlock Web Audio on iOS
            var node = context.createBufferSource();
            node.start(0);
            this._unlocked = true;
        }
    };

    /**
     * @static
     * @method _onVisibilityChange
     * @private
     */
    static _onVisibilityChange(): void {
        if (document.visibilityState === 'hidden') {
            this._onHide();
        } else {
            this._onShow();
        }
    };

    /**
     * @static
     * @method _onHide
     * @private
     */
    static _onHide(): void {
        if (this._shouldMuteOnHide()) {
            this._fadeOut(1);
        }
    };

    /**
     * @static
     * @method _onShow
     * @private
     */
    static _onShow(): void {
        if (this._shouldMuteOnHide()) {
            this._fadeIn(0.5);
        }
    };

    /**
     * @static
     * @method _shouldMuteOnHide
     * @private
     */
    static _shouldMuteOnHide(): boolean {
        return Utils.isMobileDevice();
    };

    /**
     * @static
     * @method _fadeIn
     * @param {Number} duration
     * @private
     */
    static _fadeIn(duration: number): void {
        if (this._masterGainNode) {
            var gain = this._masterGainNode.gain;
            var currentTime = WebAudio._context.currentTime;
            gain.setValueAtTime(0, currentTime);
            gain.linearRampToValueAtTime(this._masterVolume, currentTime + duration);
        }
    };

    /**
     * @static
     * @method _fadeOut
     * @param {Number} duration
     * @private
     */
    static _fadeOut(duration: number): void {
        if (this._masterGainNode) {
            var gain = this._masterGainNode.gain;
            var currentTime = WebAudio._context.currentTime;
            gain.setValueAtTime(this._masterVolume, currentTime);
            gain.linearRampToValueAtTime(0, currentTime + duration);
        }
    };

    /**
     * Clears the audio data.
     *
     * @method clear
     */
    clear(): void {
        this.stop();
        this._buffer = null;
        this._sourceNode = null;
        this._gainNode = null;
        this._pannerNode = null;
        this._totalTime = 0;
        this._sampleRate = 0;
        this._loopStart = 0;
        this._loopLength = 0;
        this._startTime = 0;
        this._volume = 1;
        this._pitch = 1;
        this._pan = 0;
        this._endTimer = null;
        this._loadListeners = [];
        this._stopListeners = [];
        this._hasError = false;
        this._autoPlay = false;
    };

    /**
     * [read-only] The url of the audio file.
     *
     * @property url
     * @type String
     */
    //url: string;
    public get url() {
        return this._url;
    }

    /**
     * The volume of the audio.
     *
     * @property volume
     * @type Number
     */
    public get volume() {
        return this._volume;
    }

    public set volume(value: number) {
        this._volume = value;
        if (this._gainNode) {
            this._gainNode.gain.setValueAtTime(this._volume, WebAudio._context.currentTime);
        }
    }


    /**
     * The pitch of the audio.
     *
     * @property pitch
     * @type Number
     */
    public get pitch() {
        return this._pitch;
    }
    public set pitch(value: number) {
        if (this._pitch !== value) {
            this._pitch = value;
            if (this.isPlaying()) {
                this.play(this._sourceNode.loop, 0);
            }
        }
    }


    /**
     * The pan of the audio.
     *
     * @property pan
     * @type Number
     */
  
    public get pan() {
        return this._pan;
    }
    public set pan(value: number) {
        this._pan = value;
        this._updatePanner();
    }


    /**
     * Checks whether the audio data is ready to play.
     *
     * @method isReady
     * @return {Boolean} True if the audio data is ready to play
     */
    isReady(): boolean {
        return !!this._buffer;
    };

    /**
     * Checks whether a loading error has occurred.
     *
     * @method isError
     * @return {Boolean} True if a loading error has occurred
     */
    isError(): boolean {
        return this._hasError;
    };

    /**
     * Checks whether the audio is playing.
     *
     * @method isPlaying
     * @return {Boolean} True if the audio is playing
     */
    isPlaying(): boolean {
        return !!this._sourceNode;
    };

    /**
     * Plays the audio.
     *
     * @method play
     * @param {Boolean} loop Whether the audio data play in a loop
     * @param {Number} offset The start position to play in seconds
     */
    play(loop: boolean, offset: number): void {
        if (this.isReady()) {
            offset = offset || 0;
            this._startPlaying(loop, offset);
        } else if (WebAudio._context) {
            this._autoPlay = true;
            this.addLoadListener(function(this:any) {
                if (this._autoPlay) {
                    this.play(loop, offset);
                }
            }.bind(this));
        }
    };

    /**
     * Stops the audio.
     *
     * @method stop
     */
    stop(): void {
        this._autoPlay = false;
        this._removeEndTimer();
        this._removeNodes();
        if (this._stopListeners) {
            while (this._stopListeners.length > 0) {
                var listner = this._stopListeners.shift();
                listner();
            }
        }
    };

    /**
     * Performs the audio fade-in.
     *
     * @method fadeIn
     * @param {Number} duration Fade-in time in seconds
     */
    fadeIn(duration: number): void {
        if (this.isReady()) {
            if (this._gainNode) {
                var gain = this._gainNode.gain;
                var currentTime = WebAudio._context.currentTime;
                gain.setValueAtTime(0, currentTime);
                gain.linearRampToValueAtTime(this._volume, currentTime + duration);
            }
        } else if (this._autoPlay) {
            this.addLoadListener(function(this:any) {
                this.fadeIn(duration);
            }.bind(this));
        }
    };

    /**
     * Performs the audio fade-out.
     *
     * @method fadeOut
     * @param {Number} duration Fade-out time in seconds
     */
    fadeOut(duration: number): void {
        if (this._gainNode) {
            var gain = this._gainNode.gain;
            var currentTime = WebAudio._context.currentTime;
            gain.setValueAtTime(this._volume, currentTime);
            gain.linearRampToValueAtTime(0, currentTime + duration);
        }
        this._autoPlay = false;
    };

    /**
     * Gets the seek position of the audio.
     *
     * @method seek
     */
    seek(): number {
        if (WebAudio._context) {
            var pos = (WebAudio._context.currentTime - this._startTime) * this._pitch;
            if (this._loopLength > 0) {
                while (pos >= this._loopStart + this._loopLength) {
                    pos -= this._loopLength;
                }
            }
            return pos;
        } else {
            return 0;
        }
    };

    /**
     * Add a callback function that will be called when the audio data is loaded.
     *
     * @method addLoadListener
     * @param {Function} listner The callback function
     */
    addLoadListener(listner: Function): void {
        this._loadListeners.push(listner);
    };

    /**
     * Add a callback function that will be called when the playback is stopped.
     *
     * @method addStopListener
     * @param {Function} listner The callback function
     */
    addStopListener(listner: Function): void {
        this._stopListeners.push(listner);
    };

    /**
     * @method _load
     * @param {String} url
     * @private
     */
    _load(url: string): void {
        if (WebAudio._context) {
            var xhr = new XMLHttpRequest();
            if(Decrypter.hasEncryptedAudio) url = Decrypter.extToEncryptExt(url);
            xhr.open('GET', url);
            xhr.responseType = 'arraybuffer';
            xhr.onload = function(this:any) {
                if (xhr.status < 400) {
                    this._onXhrLoad(xhr);
                }
            }.bind(this);
            xhr.onerror = this._loader || function(this:any){this._hasError = true;}.bind(this);
            xhr.send();
        }
    };

    /**
     * @method _onXhrLoad
     * @param {XMLHttpRequest} xhr
     * @private
     */
    _onXhrLoad(xhr: XMLHttpRequest): void {
        var array = xhr.response;
        if(Decrypter.hasEncryptedAudio) array = Decrypter.decryptArrayBuffer(array);
        this._readLoopComments(new Uint8Array(array));
        WebAudio._context.decodeAudioData(array, function(this:any,buffer:any) {
            this._buffer = buffer;
            this._totalTime = buffer.duration;
            if (this._loopLength > 0 && this._sampleRate > 0) {
                this._loopStart /= this._sampleRate;
                this._loopLength /= this._sampleRate;
            } else {
                this._loopStart = 0;
                this._loopLength = this._totalTime;
            }
            this._onLoad();
        }.bind(this));
    };

    /**
     * @method _startPlaying
     * @param {Boolean} loop
     * @param {Number} offset
     * @private
     */
    _startPlaying(loop: boolean, offset: number): void {
        if (this._loopLength > 0) {
         while (offset >= this._loopStart + this._loopLength) {
         offset -= this._loopLength;
         }
        }
        this._removeEndTimer();
        this._removeNodes();
        this._createNodes();
        this._connectNodes();
        this._sourceNode.loop = loop;
        this._sourceNode.start(0, offset);
        this._startTime = WebAudio._context.currentTime - offset / this._pitch;
        this._createEndTimer();
    };

    /**
     * @method _createNodes
     * @private
     */
    _createNodes(): void {
        var context = WebAudio._context;
        this._sourceNode = context.createBufferSource();
        this._sourceNode.buffer = this._buffer;
        this._sourceNode.loopStart = this._loopStart;
        this._sourceNode.loopEnd = this._loopStart + this._loopLength;
        this._sourceNode.playbackRate.setValueAtTime(this._pitch, context.currentTime);
        this._gainNode = context.createGain();
        this._gainNode.gain.setValueAtTime(this._volume, context.currentTime);
        this._pannerNode = context.createPanner();
        this._pannerNode.panningModel = 'equalpower';
        this._updatePanner();
    };

    /**
     * @method _connectNodes
     * @private
     */
    _connectNodes(): void {
        this._sourceNode.connect(this._gainNode);
        this._gainNode.connect(this._pannerNode);
        this._pannerNode.connect(WebAudio._masterGainNode);
    };

    /**
     * @method _removeNodes
     * @private
     */
    _removeNodes(): void {
        if (this._sourceNode) {
            this._sourceNode.stop(0);
            this._sourceNode = null;
            this._gainNode = null;
            this._pannerNode = null;
        }
    };

    /**
     * @method _createEndTimer
     * @private
     */
    _createEndTimer(): void {
        if (this._sourceNode && !this._sourceNode.loop) {
            var endTime = this._startTime + this._totalTime / this._pitch;
            var delay =  endTime - WebAudio._context.currentTime;
            this._endTimer = setTimeout(function(this:any) {
                this.stop();
            }.bind(this), delay * 1000);
        }
    };


    /**
     * @method _removeEndTimer
     * @private
     */
    _removeEndTimer(): void {
        if (this._endTimer) {
            clearTimeout(this._endTimer);
            this._endTimer = null;
        }
    };

    /**
     * @method _updatePanner
     * @private
     */
    _updatePanner(): void {
        if (this._pannerNode) {
            var x = this._pan;
            var z = 1 - Math.abs(x);
            this._pannerNode.setPosition(x, 0, z);
        }
    };

    /**
     * @method _onLoad
     * @private
     */
    _onLoad(): void {
        while (this._loadListeners.length > 0) {
            var listner = this._loadListeners.shift();
            listner();
        }
    };

    /**
     * @method _readLoopComments
     * @param {Uint8Array} array
     * @private
     */
    _readLoopComments(array: Uint8Array): void {
        this._readOgg(array);
        this._readMp4(array);
    };

    /**
     * @method _readOgg
     * @param {Uint8Array} array
     * @private
     */
    _readOgg(array: Uint8Array): void {
        var index = 0;
        while (index < array.length) {
            if (this._readFourCharacters(array, index) === 'OggS') {
                index += 26;
                var vorbisHeaderFound = false;
                var numSegments = array[index++];
                var segments = [];
                for (var i = 0; i < numSegments; i++) {
                    segments.push(array[index++]);
                }
                for (i = 0; i < numSegments; i++) {
                    if (this._readFourCharacters(array, index + 1) === 'vorb') {
                        var headerType = array[index];
                        if (headerType === 1) {
                            this._sampleRate = this._readLittleEndian(array, index + 12);
                        } else if (headerType === 3) {
                            this._readMetaData(array, index, segments[i]);
                        }
                        vorbisHeaderFound = true;
                    }
                    index += segments[i];
                }
                if (!vorbisHeaderFound) {
                    break;
                }
            } else {
                break;
            }
        }
    };

    /**
     * @method _readMp4
     * @param {Uint8Array} array
     * @private
     */
    _readMp4(array: Uint8Array): void {
        if (this._readFourCharacters(array, 4) === 'ftyp') {
            var index = 0;
            while (index < array.length) {
                var size = this._readBigEndian(array, index);
                var name = this._readFourCharacters(array, index + 4);
                if (name === 'moov') {
                    index += 8;
                } else {
                    if (name === 'mvhd') {
                        this._sampleRate = this._readBigEndian(array, index + 20);
                    }
                    if (name === 'udta' || name === 'meta') {
                        this._readMetaData(array, index, size);
                    }
                    index += size;
                    if (size <= 1) {
                        break;
                    }
                }
            }
        }
    };

    /**
     * @method _readMetaData
     * @param {Uint8Array} array
     * @param {Number} index
     * @param {Number} size
     * @private
     */
    _readMetaData(array: Uint8Array, index: number, size: any): void {
        for (var i = index; i < index + size - 10; i++) {
            if (this._readFourCharacters(array, i) === 'LOOP') {
                var text = '';
                while (array[i] > 0) {
                    text += String.fromCharCode(array[i++]);
                }
                if (text.match(/LOOPSTART=([0-9]+)/)) {
                    this._loopStart = parseInt(RegExp.$1);
                }
                if (text.match(/LOOPLENGTH=([0-9]+)/)) {
                    this._loopLength = parseInt(RegExp.$1);
                }
                if (text == 'LOOPSTART' || text == 'LOOPLENGTH') {
                    var text2 = '';
                    i += 16;
                    while (array[i] > 0) {
                        text2 += String.fromCharCode(array[i++]);
                    }
                    if (text == 'LOOPSTART') {
                        this._loopStart = parseInt(text2);
                    } else {
                        this._loopLength = parseInt(text2);
                    }
                }
            }
        }
    };

    /**
     * @method _readLittleEndian
     * @param {Uint8Array} array
     * @param {Number} index
     * @private
     */
    _readLittleEndian(array: Uint8Array, index: number): any {
        return (array[index + 3] * 0x1000000 + array[index + 2] * 0x10000 +
                array[index + 1] * 0x100 + array[index + 0]);
    };

    /**
     * @method _readBigEndian
     * @param {Uint8Array} array
     * @param {Number} index
     * @private
     */
    _readBigEndian(array: Uint8Array, index: number): any {
        return (array[index + 0] * 0x1000000 + array[index + 1] * 0x10000 +
                array[index + 2] * 0x100 + array[index + 3]);
    };

    /**
     * @method _readFourCharacters
     * @param {Uint8Array} array
     * @param {Number} index
     * @private
     */
    _readFourCharacters(array: Uint8Array, index: number): any {
        var string = '';
        for (var i = 0; i < 4; i++) {
            string += String.fromCharCode(array[index + i]);
        }
        return string;
    };

    static _canPlayOgg: CanPlayTypeResult;
    static _canPlayM4a: CanPlayTypeResult;
    _url: string;
    _buffer: AudioBuffer;
    _sourceNode: AudioBufferSourceNode;
    _gainNode: GainNode;
    _pannerNode: PannerNode;
    _totalTime: number;
    _sampleRate: any;
    _loopStart: number;
    _loopLength: number;
    _startTime: number;
    _volume: number;
    _pitch: number;
    _pan: number;
    _endTimer: any;
    _loadListeners: Function[];
    _stopListeners: Function[];
    _hasError: boolean;
    _autoPlay: boolean;
    _loader:any;

   
}

WebAudio._standAlone = (function(top:any){
    return !top.ResourceHandler;
})(this);

//-----------------------------------------------------------------------------
/**
 * The static class that handles HTML5 Audio.
 *
 * @class Html5Audio
 * @constructor
 */
class Html5Audio {
    private constructor(){
        throw new Error('This is a static class');
    }

    static _initialized: boolean= false;
    static _unlocked: boolean= false;
    static _audioElement: HTMLAudioElement = null;
    static _gainTweenInterval: any = null;
    static _tweenGain: number = 0;
    static _tweenTargetGain: number = 0;
    static _tweenGainStep: number = 0;
    static _staticSePath: string = null;

    /**
     * Sets up the Html5 Audio.
     *
     * @static
     * @method setup
     * @param {String} url The url of the audio file
     */
    static setup(url: string): void {
        if (!this._initialized) {
            this.initialize();
        }
        this.clear();
    
        if(Decrypter.hasEncryptedAudio && this._audioElement.src) {
            window.URL.revokeObjectURL(this._audioElement.src);
        }
        this._url = url;
    };

    /**
     * Initializes the audio system.
     *
     * @static
     * @method initialize
     * @return {Boolean} True if the audio system is available
     */
    static initialize(): boolean {
        if (!this._initialized) {
            if (!this._audioElement) {
                try {
                    this._audioElement = new Audio();
                } catch (e) {
                    this._audioElement = null;
                }
            }
            if (!!this._audioElement) this._setupEventHandlers();
            this._initialized = true;
        }
        return !!this._audioElement;
    };
    

    /**
     * @static
     * @method _setupEventHandlers
     * @private
     */
    static _setupEventHandlers(): void {
        document.addEventListener('touchstart', this._onTouchStart.bind(this));
        document.addEventListener('visibilitychange', this._onVisibilityChange.bind(this));
        this._audioElement.addEventListener("loadeddata", this._onLoadedData.bind(this));
        this._audioElement.addEventListener("error", this._onError.bind(this));
        this._audioElement.addEventListener("ended", this._onEnded.bind(this));
    };

    /**
     * @static
     * @method _onTouchStart
     * @private
     */
    static _onTouchStart(): void {
        if (this._audioElement && !this._unlocked) {
            if (this._isLoading) {
                this._load(this._url);
                this._unlocked = true;
            } else {
                if (this._staticSePath) {
                    this._audioElement.src = this._staticSePath;
                    this._audioElement.volume = 0;
                    this._audioElement.loop = false;
                    this._audioElement.play();
                    this._unlocked = true;
                }
            }
        }
    };

    /**
     * @static
     * @method _onVisibilityChange
     * @private
     */
    static _onVisibilityChange(): void {
        if (document.visibilityState === 'hidden') {
            this._onHide();
        } else {
            this._onShow();
        }
    };

    /**
     * @static
     * @method _onLoadedData
     * @private
     */
    static _onLoadedData(): void {
        this._buffered = true;
        if (this._unlocked) this._onLoad();
    };

    /**
     * @static
     * @method _onError
     * @private
     */
    static _onError(): void {
        this._hasError = true;
    };

    /**
     * @static
     * @method _onEnded
     * @private
     */
    static _onEnded(): void {
        if (!this._audioElement.loop) {
            this.stop();
        }
    };

    /**
     * @static
     * @method _onHide
     * @private
     */
    static _onHide(): void {
        this._audioElement.volume = 0;
        this._tweenGain = 0;
    };

    /**
     * @static
     * @method _onShow
     * @private
     */
    static _onShow(): void {
        this.fadeIn(0.5);
    };

    /**
     * Clears the audio data.
     *
     * @static
     * @method clear
     */
    static clear(): void {
        this.stop();
        this._volume = 1;
        this._loadListeners = [];
        this._hasError = false;
        this._autoPlay = false;
        this._isLoading = false;
        this._buffered = false;
    };


    /**
     * Set the URL of static se.
     *
     * @static
     * @param {String} url
     */
    static setStaticSe(url: string): void {
        if (!this._initialized) {
            this.initialize();
            this.clear();
        }
        this._staticSePath = url;
    };

    /**
     * [read-only] The url of the audio file.
     *
     * @property url
     * @type String
     */
    //static url: string;
    public static get url() {
        return Html5Audio._url;
    }

    /**
     * The volume of the audio.
     *
     * @property volume
     * @type Number
     */
    //static volume: string;
    public static get volume() {
        return Html5Audio._volume;
    }
    
    public static set volume(value: string) {
        Html5Audio._volume = value;
        if (Html5Audio._audioElement) {
            Html5Audio._audioElement.volume = this._volume;
        }
    }

    /**
     * Checks whether the audio data is ready to play.
     *
     * @static
     * @method isReady
     * @return {Boolean} True if the audio data is ready to play
     */
    static isReady(): boolean {
        return this._buffered;
    };

    /**
     * Checks whether a loading error has occurred.
     *
     * @static
     * @method isError
     * @return {Boolean} True if a loading error has occurred
     */
    static isError(): boolean {
        return this._hasError;
    };

    /**
     * Checks whether the audio is playing.
     *
     * @static
     * @method isPlaying
     * @return {Boolean} True if the audio is playing
     */
    static isPlaying(): boolean {
        return !this._audioElement.paused;
    };

    /**
     * Plays the audio.
     *
     * @static
     * @method play
     * @param {Boolean} loop Whether the audio data play in a loop
     * @param {Number} offset The start position to play in seconds
     */
    static play(loop: boolean, offset: number): void {
        if (this.isReady()) {
            offset = offset || 0;
            this._startPlaying(loop, offset);
        } else if (Html5Audio._audioElement) {
            this._autoPlay = true;
            this.addLoadListener(function (this:any) {
                if (this._autoPlay) {
                    this.play(loop, offset);
                    if (this._gainTweenInterval) {
                        clearInterval(this._gainTweenInterval);
                        this._gainTweenInterval = null;
                    }
                }
            }.bind(this));
            if (!this._isLoading) this._load(this._url);
        }
    };

    /**
     * Stops the audio.
     *
     * @static
     * @method stop
     */   
    static stop(): void {
        if (this._audioElement) this._audioElement.pause();
        this._autoPlay = false;
        if (this._tweenInterval) {
            clearInterval(this._tweenInterval);
            this._tweenInterval = null;
            this._audioElement.volume = 0;
        }
    };

    /**
     * Performs the audio fade-in.
     *
     * @static
     * @method fadeIn
     * @param {Number} duration Fade-in time in seconds
     */   
    static fadeIn(duration: number): void {
        if (this.isReady()) {
            if (this._audioElement) {
                this._tweenTargetGain = this._volume;
                this._tweenGain = 0;
                this._startGainTween(duration);
            }
        } else if (this._autoPlay) {
            this.addLoadListener(function (this:any) {
                this.fadeIn(duration);
            }.bind(this));
        }
    };

    /**
     * Performs the audio fade-out.
     *
     * @static
     * @method fadeOut
     * @param {Number} duration Fade-out time in seconds
     */
    static fadeOut(duration: number): void {
        if (this._audioElement) {
            this._tweenTargetGain = 0;
            this._tweenGain = this._volume;
            this._startGainTween(duration);
        }
    };

    /**
     * Gets the seek position of the audio.
     *
     * @static
     * @method seek
     */
    static seek(): number {
        if (this._audioElement) {
            return this._audioElement.currentTime;
        } else {
            return 0;
        }
    };

    /**
     * Add a callback function that will be called when the audio data is loaded.
     *
     * @static
     * @method addLoadListener
     * @param {Function} listner The callback function
     */
    static addLoadListener(listner: Function): void {
        this._loadListeners.push(listner);
    };

    /**
     * @static
     * @method _load
     * @param {String} url
     * @private
     */
    static _load(url: string): void {
        if (this._audioElement) {
            this._isLoading = true;
            this._audioElement.src = url;
            this._audioElement.load();
        }
    };

    /**
     * @static
     * @method _startPlaying
     * @param {Boolean} loop
     * @param {Number} offset
     * @private
     */   
    static _startPlaying(loop: boolean, offset: number): void {
        this._audioElement.loop = loop;
        if (this._gainTweenInterval) {
            clearInterval(this._gainTweenInterval);
            this._gainTweenInterval = null;
        }
        if (this._audioElement) {
            this._audioElement.volume = this._volume;
            this._audioElement.currentTime = offset;
            this._audioElement.play();
        }
    };

    /**
     * @static
     * @method _onLoad
     * @private
     */
    static _onLoad(): void {
        this._isLoading = false;
        while (this._loadListeners.length > 0) {
            var listener = this._loadListeners.shift();
            listener();
        }
    };

    /**
     * @static
     * @method _startGainTween
     * @params {Number} duration
     * @private
     */
    static _startGainTween(duration: number): void {
        this._audioElement.volume = this._tweenGain;
        if (this._gainTweenInterval) {
            clearInterval(this._gainTweenInterval);
            this._gainTweenInterval = null;
        }
        this._tweenGainStep = (this._tweenTargetGain - this._tweenGain) / (60 * duration);
        this._gainTweenInterval = setInterval(function () {
            Html5Audio._applyTweenValue(Html5Audio._tweenTargetGain);
        }, 1000 / 60);
    };

    /**
     * @static
     * @method _applyTweenValue
     * @param {Number} volume
     * @private
     */
    static _applyTweenValue(volume: number): void {
        Html5Audio._tweenGain += Html5Audio._tweenGainStep;
        if (Html5Audio._tweenGain < 0 && Html5Audio._tweenGainStep < 0) {
            Html5Audio._tweenGain = 0;
        }
        else if (Html5Audio._tweenGain > volume && Html5Audio._tweenGainStep > 0) {
            Html5Audio._tweenGain = volume;
        }
    
        if (Math.abs(Html5Audio._tweenTargetGain - Html5Audio._tweenGain) < 0.01) {
            Html5Audio._tweenGain = Html5Audio._tweenTargetGain;
            clearInterval(Html5Audio._gainTweenInterval);
            Html5Audio._gainTweenInterval = null;
        }
    
        Html5Audio._audioElement.volume = Html5Audio._tweenGain;
    };

     
    static _tweenInterval:any;
    static _url: string;
    static _buffered: boolean;
    static _hasError: boolean;
    static _autoPlay: boolean;
    static _isLoading: boolean;
    static _loadListeners: Function[];
    static _volume: any;
}

//-----------------------------------------------------------------------------
/**
 * The static class that handles JSON with object information.
 *
 * @class JsonEx
 */
class JsonEx {
    private constructor() {
        throw new Error('This is a static class');
    }

    /**
     * The maximum depth of objects.
     *
     * @static
     * @property maxDepth
     * @type Number
     * @default 100
     */
    static maxDepth: number = 100;

    static _id: number = 1;
    static _generateId(): number{
        return JsonEx._id++;
    };

    /**
     * Converts an object to a JSON string with object information.
     *
     * @static
     * @method stringify
     * @param {Object} object The object to be converted
     * @return {String} The JSON string
     */  
    static stringify(object: any): string {
        var circular: any[] = [];
        JsonEx._id = 1;
        var json = JSON.stringify(this._encode(object, circular, 0));
        this._cleanMetadata(object);
        this._restoreCircularReference(circular);
    
        return json;
    };

    static _restoreCircularReference(circulars: any[]): void{
        circulars.forEach(function(circular){
            var key = circular[0];
            var value = circular[1];
            var content = circular[2];

            value[key] = content;
        });
    };

    /**
     * Parses a JSON string and reconstructs the corresponding object.
     *
     * @static
     * @method parse
     * @param {String} json The JSON string
     * @return {Object} The reconstructed object
     */   
    static parse(json: string): any {
        var circular:any[] = [];
        var registry:any = {};
        var contents = this._decode(JSON.parse(json), circular, registry);
        this._cleanMetadata(contents);
        this._linkCircularReference(contents, circular, registry);
    
        return contents;
    };

    static _linkCircularReference(contents: any, circulars: any[], registry: any[]): void{
        circulars.forEach(function(circular){
            var key = circular[0];
            var value = circular[1];
            var id = circular[2];
    
            value[key] = registry[id];
        });
    };

    static _cleanMetadata(object: any): void{
        if(!object) return;
    
        delete object['@'];
        delete object['@c'];
    
        if(typeof object === 'object'){
            Object.keys(object).forEach(function(key){
                var value = object[key];
                if(typeof value === 'object'){
                    JsonEx._cleanMetadata(value);
                }
            });
        }
    };

    /**
     * Makes a deep copy of the specified object.
     *
     * @static
     * @method makeDeepCopy
     * @param {Object} object The object to be copied
     * @return {Object} The copied object
     */
    static makeDeepCopy(object: any): any {
        return this.parse(this.stringify(object));
    };

    /**
     * @static
     * @method _encode
     * @param {Object} value
     * @param {Array} circular
     * @param {Number} depth
     * @return {Object}
     * @private
     */
    static _encode(value: any, circular: any[], depth: number): any {
        depth = depth || 0;
        if (++depth >= this.maxDepth) {
            throw new Error('Object too deep');
        }
        var type = Object.prototype.toString.call(value);
        if (type === '[object Object]' || type === '[object Array]') {
            value['@c'] = JsonEx._generateId();
    
            var constructorName = this._getConstructorName(value);
            if (constructorName !== 'Object' && constructorName !== 'Array') {
                value['@'] = constructorName;
            }
            for (var key in value) {
                if (value.hasOwnProperty(key) && !key.match(/^@./)) {
                    if(value[key] && typeof value[key] === 'object'){
                        if(value[key]['@c']){
                            circular.push([key, value, value[key]]);
                            value[key] = {'@r': value[key]['@c']};
                        }else{
                            value[key] = this._encode(value[key], circular, depth + 1);
    
                            if(value[key] instanceof Array){
                                //wrap array
                                circular.push([key, value, value[key]]);
    
                                value[key] = {
                                    '@c': value[key]['@c'],
                                    '@a': value[key]
                                };
                            }
                        }
                    }else{
                        value[key] = this._encode(value[key], circular, depth + 1);
                    }
                }
            }
        }
        depth--;
        return value;
    };

    /**
     * @static
     * @method _decode
     * @param {Object} value
     * @param {Array} circular
     * @param {Object} registry
     * @return {Object}
     * @private
     */
    static _decode(value: any, circular: any[], registry: any): any {
        var type = Object.prototype.toString.call(value);
        if (type === '[object Object]' || type === '[object Array]') {
            registry[value['@c']] = value;
    
            if (value['@']) {
                var constructor = window[value['@']];
                if (constructor) {
                    value = this._resetPrototype(value, constructor.prototype);
                }
            }
            for (var key in value) {
                if (value.hasOwnProperty(key)) {
                    if(value[key] && value[key]['@a']){
                        //object is array wrapper
                        var body = value[key]['@a'];
                        body['@c'] = value[key]['@c'];
                        value[key] = body;
                    }
                    if(value[key] && value[key]['@r']){
                        //object is reference
                        circular.push([key, value, value[key]['@r']])
                    }
                    value[key] = this._decode(value[key], circular, registry);
                }
            }
        }
        return value;
    };

    /**
     * @static
     * @method _getConstructorName
     * @param {Object} value
     * @return {String}
     * @private
     */
    static _getConstructorName(value: any): any{
        var name = value.constructor.name;
        if (name === undefined) {
            var func = /^\s*function\s*([A-Za-z0-9_$]*)/;
            name = func.exec(value.constructor)[1];
        }
        return name;
    };

    /**
     * @static
     * @method _resetPrototype
     * @param {Object} value
     * @param {Object} prototype
     * @return {Object}
     * @private
     */
    static _resetPrototype(value: any, prototype: any): any {
        if (Object.setPrototypeOf !== undefined) {
            Object.setPrototypeOf(value, prototype);
        } else if ('__proto__' in value) {
            value.__proto__ = prototype;
        } else {
            var newValue = Object.create(prototype);
            for (var key in value) {
                if (value.hasOwnProperty(key)) {
                    newValue[key] = value[key];
                }
            }
            value = newValue;
        }
        return value;
    };
}

class Decrypter {
    private constructor() {
        throw new Error('This is a static class');
    }

    static hasEncryptedImages: boolean = false;
    static hasEncryptedAudio: boolean = false;

    static _requestImgFile: any[] = [];
    static _headerlength: number = 16;
    static _xhrOk: number = 400;
    static _encryptionKey: string = "";
    static _ignoreList: string[] =  [
        "img/system/Window.png"
    ];

    static SIGNATURE: string= "5250474d56000000";
    static VER: string = "000301";
    static REMAIN: string = "0000000000";

    static checkImgIgnore(url: string): boolean {
        for(var cnt = 0; cnt < this._ignoreList.length; cnt++) {
            if(url === this._ignoreList[cnt]) return true;
        }
        return false;
    };
    static decryptImg(url: string, bitmap: Bitmap): void {
        url = this.extToEncryptExt(url);
    
        var requestFile = new XMLHttpRequest();
        requestFile.open("GET", url);
        requestFile.responseType = "arraybuffer";
        requestFile.send();
    
        requestFile.onload = function () {
            if(this.status < Decrypter._xhrOk) {
                var arrayBuffer = Decrypter.decryptArrayBuffer(requestFile.response);
                bitmap._image.src = Decrypter.createBlobUrl(arrayBuffer);
                bitmap._image.addEventListener('load', bitmap._loadListener = Bitmap.prototype._onLoad.bind(bitmap));
                bitmap._image.addEventListener('error', bitmap._errorListener = bitmap._loader || Bitmap.prototype._onError.bind(bitmap));
            }
        };
    
        requestFile.onerror = function () {
            if (bitmap._loader) {
                bitmap._loader();
            } else {
                bitmap._onError();
            }
        };
    };

    static decryptHTML5Audio(url: string, bgm: RPGMakerMV.AudioObject, pos: number): void {
        var requestFile = new XMLHttpRequest();
        requestFile.open("GET", url);
        requestFile.responseType = "arraybuffer";
        requestFile.send();
    
        requestFile.onload = function () {
            if(this.status < Decrypter._xhrOk) {
                var arrayBuffer = Decrypter.decryptArrayBuffer(requestFile.response);
                var url = Decrypter.createBlobUrl(arrayBuffer);
                AudioManager.createDecryptBuffer(url, bgm, pos);
            }
        };
    };

    static cutArrayHeader(arrayBuffer: ArrayBuffer, length: number): ArrayBuffer {
        return arrayBuffer.slice(length);
    };
    static decryptArrayBuffer(arrayBuffer: ArrayBuffer): ArrayBuffer {
        if (!arrayBuffer) return null;
        var header = new Uint8Array(arrayBuffer, 0, this._headerlength);
    
        var i;
        var ref = this.SIGNATURE + this.VER + this.REMAIN;
        var refBytes = new Uint8Array(16);
        for (i = 0; i < this._headerlength; i++) {
            refBytes[i] = parseInt("0x" + ref.substr(i * 2, 2), 16);
        }
        for (i = 0; i < this._headerlength; i++) {
            if (header[i] !== refBytes[i]) {
                throw new Error("Header is wrong");
            }
        }
    
        arrayBuffer = this.cutArrayHeader(arrayBuffer, Decrypter._headerlength);
        var view = new DataView(arrayBuffer);
        this.readEncryptionkey();
        if (arrayBuffer) {
            var byteArray = new Uint8Array(arrayBuffer);
            for (i = 0; i < this._headerlength; i++) {
                byteArray[i] = byteArray[i] ^ parseInt(Decrypter._encryptionKey[i], 16);
                view.setUint8(i, byteArray[i]);
            }
        }
    
        return arrayBuffer;
    };

    static createBlobUrl(arrayBuffer: ArrayBuffer): string{
        var blob = new Blob([arrayBuffer]);
        return window.URL.createObjectURL(blob);
    };
    static extToEncryptExt(url: string): string {
        var ext = url.split('.').pop();
        var encryptedExt = ext;
    
        if(ext === "ogg") encryptedExt = ".rpgmvo";
        else if(ext === "m4a") encryptedExt = ".rpgmvm";
        else if(ext === "png") encryptedExt = ".rpgmvp";
        else encryptedExt = ext;
    
        return url.slice(0, url.lastIndexOf(ext) - 1) + encryptedExt;
    };

    static readEncryptionkey(): void{
        this._encryptionKey = $dataSystem.encryptionKey.split(/(.{2})/).filter(Boolean);
    };
}

//-----------------------------------------------------------------------------
/**
 * The static class that handles resource loading.
 *
 * @class ResourceHandler
 */
class ResourceHandler {
    private constructor() {
        throw new Error('This is a static class');
    }

    static _reloaders: Function[] = [];
    static _defaultRetryInterval: number[] = [500, 1000, 3000];

    static createLoader(url: string, retryMethod: Function, resignMethod?: Function, retryInterval?: any): Function {
        retryInterval = retryInterval || this._defaultRetryInterval;
        var reloaders = this._reloaders;
        var retryCount = 0;
        return function() {
            if (retryCount < retryInterval.length) {
                setTimeout(retryMethod, retryInterval[retryCount]);
                retryCount++;
            } else {
                if (resignMethod) {
                    resignMethod();
                }
                if (url) {
                    if (reloaders.length === 0) {
                        Graphics.printLoadingError(url);
                        SceneManager.stop();
                    }
                    reloaders.push(function() {
                        retryCount = 0;
                        retryMethod();
                    });
                }
            }
        };
    };

    static exists(): boolean {
        return this._reloaders.length > 0;
    };
    static retry(): void {
        if (this._reloaders.length > 0) {
            Graphics.eraseLoadingError();
            SceneManager.resume();
            this._reloaders.forEach(function(reloader) {
                reloader();
            });
            this._reloaders.length = 0;
        }
    };
}

